ion oauth

This commit is contained in:
lauren 2020-10-27 10:23:59 -04:00
parent b1aea97fc3
commit 3c225f0014
21 changed files with 237 additions and 123 deletions

View File

@ -0,0 +1 @@
default_app_config = "studyguides.apps.auth.apps.AuthConfig"

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View File

@ -1,40 +1,36 @@
from typing import Any, Dict, List
from social_core.backends.oauth import BaseOAuth2
from social_core.pipeline.user import get_username as social_get_username
def get_username(strategy, details, user = None, *args, **kwargs):
result = social_get_username(strategy, details, user = user, *args, **kwargs)
def get_username(strategy, details, *args, user=None, **kwargs):
result = social_get_username(strategy, details, user=user, *args, **kwargs)
return result
class IonOauth2(BaseOAuth2):
name = 'ion'
AUTHORIZATION_URL = 'https://ion.tjhsst.edu/oauth/authorize'
ACCESS_TOKEN_URL = 'https://ion.tjhsst.edu/oauth/token'
ACCESS_TOKEN_METHOD = 'POST'
EXTRA_DATA = [
('refresh_token', 'refresh_token', True),
('expires_in', 'expires')
]
name = "ion"
AUTHORIZATION_URL = "https://ion.tjhsst.edu/oauth/authorize"
ACCESS_TOKEN_URL = "https://ion.tjhsst.edu/oauth/token"
ACCESS_TOKEN_METHOD = "POST"
EXTRA_DATA = [("refresh_token", "refresh_token", True), ("expires_in", "expires")]
def get_scope(self):
def get_scope(self) -> List[str]:
return ["read"]
def get_user_details(self, response):
def get_user_details(self, response: Dict[str, Any]) -> Dict[str, Any]:
profile = self.get_json(
'https://ion.tjhsst.edu/api/profile',
params = {'access_token': response['access_token']},
"https://ion.tjhsst.edu/api/profile", params={"access_token": response["access_token"]},
)
# fields used to populate/update User model
return {
'id': profile['id'],
'username': profile['ion_username'],
'first_name': profile['first_name'],
'last_name': profile['last_name'],
'full_name': profile['full_name'],
'email': profile['tj_email'],
data = {
key: profile[key]
for key in ("first_name", "last_name", "id", "is_student", "is_teacher")
}
data["username"] = profile["ion_username"]
data["email"] = profile["tj_email"]
return data
def get_user_id(self, details, response):
return details["id"]
def get_user_id(self, details: Dict[str, Any], response: Any) -> int:
return details["id"]

View File

@ -5,7 +5,7 @@ from . import views
app_name = "auth"
urlpatterns = [
path("login", views.login, name = "login"),
path("logout", views.logout, name = "logout"),
path("", views.index, name="index"),
path("login/", views.login, name="login"),
]

View File

@ -1,10 +1,10 @@
from django.shortcuts import render
from django.contrib.auth.views import LogoutView
from django.shortcuts import redirect, render
# Create your views here.
def index(request):
if request.user.is_authenticated:
return redirect("home:index")
else:
return redirect("auth:login")
def login(request):
return render(request, "auth/login.html")
logout = LogoutView.as_view()
return render(request, "login.html")

View File

@ -5,7 +5,7 @@ from . import views
app_name = "courses"
urlpatterns = [
path("<str:subject_url>/", views.subject_view),
path("subject/<str:subject_url>/", views.subject_view),
path("tag/<str:tag>/", views.tag_view, name="tag"),
path("<str:subject_url>/<str:course_url>/", views.course_view),
path("course/<str:subject_url>/<str:course_url>/", views.course_view),
]

View File

@ -2,16 +2,17 @@ import random
from django import http
from django.shortcuts import render, redirect, reverse, get_object_or_404
from django.contrib.auth.decorators import login_required
from .models import Subject, Course, Guide, Tag
@login_required
def subject_view(request, subject_url):
subject = get_object_or_404(Subject, url=subject_url)
return render(request, "subject.html", {"subject": subject,
"courses": subject.courses.all()})
@login_required
def course_view(request, subject_url, course_url):
subject = get_object_or_404(Subject, url=subject_url)
course = get_object_or_404(Course, url=course_url)
@ -19,6 +20,7 @@ def course_view(request, subject_url, course_url):
"course": course,
"guides": [[g, g.tags.all()] for g in Guide.objects.filter(course=course)]})
@login_required
def tag_view(request, tag):
tag = get_object_or_404(Tag, name=tag)
return render(request, "tag.html", {"tag": tag, "guides": [[g, g.tags.all()] for g in tag.guide.all()]})

View File

@ -4,6 +4,6 @@ from . import views
app_name = "home"
urlpatterns = [
path("", views.index_view, name = "index"),
path("all/", views.index_view, name="index"),
]

View File

@ -1,10 +1,11 @@
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.apps import apps
from django.contrib.auth.decorators import login_required
Subject = apps.get_model("courses", "Subject")
Course = apps.get_model("courses", "Course")
@login_required
def index_view(request):
return render(request, "home.html", {"subjects": [(subject, Course.objects.filter(subject=subject)) for subject in Subject.objects.all()]})

View File

@ -1,7 +1,8 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
from .models import Group, User
# Register your models here.
admin.site.register(User, UserAdmin)
admin.site.register(User)
admin.site.register(Group)

View File

@ -0,0 +1,50 @@
# Generated by Django 3.0.6 on 2020-10-27 14:01
from django.conf import settings
from django.db import migrations, models
import studyguides.apps.users.models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('id', models.AutoField(primary_key=True, serialize=False)),
('username', models.CharField(max_length=32, unique=True)),
('first_name', models.CharField(max_length=35)),
('last_name', models.CharField(max_length=70)),
('email', models.EmailField(max_length=50)),
('is_active', models.BooleanField(default=True)),
('is_service', models.BooleanField(default=False)),
('is_student', models.BooleanField(default=False)),
('is_teacher', models.BooleanField(default=False)),
('is_superuser', models.BooleanField(default=False)),
('_is_staff', models.BooleanField(default=False)),
('date_joined', models.DateTimeField(auto_now_add=True)),
],
options={
'abstract': False,
},
managers=[
('objects', studyguides.apps.users.models.UserManager()),
],
),
migrations.CreateModel(
name='Group',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('is_service', models.BooleanField(default=False)),
('name', models.CharField(max_length=32)),
('users', models.ManyToManyField(related_name='unix_groups', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,48 +1,82 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from social_django.utils import load_strategy
import requests
import logging
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.db import models
logger = logging.getLogger(__name__)
# Create your models here.
class User(AbstractUser):
id = models.AutoField(primary_key = True)
full_name = models.CharField(max_length = 105)
class UserManager(DjangoUserManager):
pass
class User(AbstractBaseUser):
objects = UserManager()
USERNAME_FIELD = "username"
EMAIL_FIELD = "email"
REQUIRED_FIELDS = ["first_name", "last_name", "email", "is_teacher"]
id = models.AutoField(primary_key=True)
username = models.CharField(unique=True, max_length=32, null=False, blank=False)
first_name = models.CharField(max_length=35, null=False, blank=False)
last_name = models.CharField(max_length=70, null=False, blank=False)
email = models.EmailField(max_length=50, null=False, blank=False)
is_active = models.BooleanField(default=True, null=False)
is_service = models.BooleanField(default=False, null=False)
is_student = models.BooleanField(default=False, null=False)
is_teacher = models.BooleanField(default=False, null=False)
is_superuser = models.BooleanField(default=False, null=False)
_is_staff = models.BooleanField(default=False, null=False)
date_joined = models.DateTimeField(auto_now_add=True)
def has_perm(self, perm, obj=None) -> bool: # pylint: disable=unused-argument
return self.is_superuser
def has_module_perms(self, app_label) -> bool: # pylint: disable=unused-argument
return self.is_superuser
@property
def short_name(self):
return self.username
def __str__(self):
def is_staff(self) -> bool:
return self._is_staff or self.is_superuser
@is_staff.setter
def is_staff(self, staff: bool) -> None:
self._is_staff = staff
@property
def full_name(self) -> str:
return self.first_name + " " + self.last_name
@property
def short_name(self) -> str:
return self.first_name
def get_full_name(self) -> str:
return self.full_name
def empty_fields(self):
list = []
for field in User._meta.fields:
list.append((field.value_from_object(self),field))
return list
def api_request(self, url, params={}, refresh=True):
s = self.get_social_auth()
params.update({"format": "json"})
params.update({"access_token": s.access_token})
r = requests.get(
"https://ion.tjhsst.edu/api/{}".format(url),
params = params,
)
if r.status_code == 401:
if refresh:
try:
self.get_social_auth().refresh_token(load_strategy())
except BaseException as e:
logger.exception(str(e))
return self.api_request(url, params, False)
else:
logger.error(
"Ion API Request Failure: {} {}".format(r.status_code,
r.json()))
return r.json()
def get_short_name(self) -> str:
return self.short_name
def get_social_auth(self):
return self.social_auth.get(provider = "ion")
return self.social_auth.get(provider="ion")
def __str__(self):
return self.username
def __repr__(self):
return "<User: {} ({})>".format(self.username, self.id)
class Group(models.Model):
id = models.AutoField(primary_key=True)
is_service = models.BooleanField(default=False)
name = models.CharField(max_length=32)
users = models.ManyToManyField(User, related_name="unix_groups")
def __str__(self):
return self.name

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,10 +0,0 @@
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [
]

View File

@ -1,6 +0,0 @@
from django.shortcuts import render, redirect
from django import http
from .models import User
from django.forms import formset_factory
# Create your views here.

View File

@ -42,9 +42,9 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'social_django',
'studyguides.apps.courses',
'studyguides.apps.home',
'studyguides.apps.auth',
'studyguides.apps.users',
'studyguides.apps.auth.apps.AuthConfig',
'studyguides.apps.home',
]
MIDDLEWARE = [
@ -72,8 +72,6 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
],
},
},
@ -111,12 +109,9 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
AUTHENTICATION_BACKENDS = (
'studyguides.apps.auth.oauth.IonOauth2',
'django.contrib.auth.backends.ModelBackend',
)
AUTHENTICATION_BACKENDS = ('studyguides.apps.auth.oauth.IonOauth2',)
SOCIAL_AUTH_USER_FIELDS = ['username', 'full_name', 'first_name', 'last_name', 'email', 'id']
SOCIAL_AUTH_USER_FIELDS = ['username', 'first_name', 'last_name', 'email', 'id', "is_teacher", "is_student",]
SOCIAL_AUTH_URL_NAMESPACE = 'social'
@ -134,8 +129,6 @@ SOCIAL_AUTH_PIPELINE = (
AUTH_USER_MODEL = "users.User"
SOCIAL_AUTH_ALWAYS_ASSOCIATE = True
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
@ -150,10 +143,12 @@ USE_L10N = True
USE_TZ = True
LOGIN_URL = "auth:login"
LOGIN_REDIRECT_URL = "users:index"
LOGOUT_REDIRECT_URL = "home:index"
LOGIN_REDIRECT_URL = "auth:index"
SOCIAL_AUTH_LOGIN_ERROR_URL = '/'
SESSION_SAVE_EVERY_REQUEST = True
SOCIAL_AUTH_ALWAYS_ASSOCIATE = True
SOCIAL_AUTH_LOGIN_ERROR_URL = "/"
SOCIAL_AUTH_RAISE_EXCEPTIONS = False
# Static files (CSS, JavaScript, Images)

View File

@ -0,0 +1,43 @@
.btn.btn-ion {
text-decoration: none;
color: #484848;
display: inline-block;
line-height: 18px;
padding: 7px 10px;
margin: 2px 0;
font-size: 13px;
font-weight: bold;
text-shadow: 0 1px 0 rgba(255,255,255,.9);
white-space: nowrap;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
background: #f7f7f4;
background: -moz-linear-gradient(top,#f7f7f4 0%,#eaeaea 100%);
background: -webkit-linear-gradient(top,#f7f7f4 0%,#eaeaea 100%);
background: linear-gradient(to bottom,#f7f7f4 0%,#eaeaea 100%);
border: 1px solid #ddd;
border-bottom-color: #c5c5c5;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.05);
-moz-box-shadow: 0 1px 3px rgba(0,0,0,.05);
-pie-box-shadow: none;
box-shadow: 0 1px 3px rgba(0,0,0,.05);
vertical-align: middle;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-appearance: none;
-moz-appearance: none;
-o-appearance: none;
appearance: none;
}
.login-box {
text-align: center;
padding: 15px;
border: 1px solid rgb(24, 82, 103);
margin: 15px auto;
max-width: 438px;
}

View File

@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% load static %}
{% block head %}
<link rel='stylesheet' href="{% static 'login.css' %}">
{% endblock %}
{% block main %}
<main>
<h1 class='header_title'>Study Guides</h1>
<div class='login-box'>
<p><b>Login in with your Intranet account</b> to access the Study Guide website.</p>
<a href="{% url 'social:begin' 'ion' %}{% if request.GET.next %}?next={{ request.GET.next|urlencode }}{% endif %}" class='btn btn-ion'>Log in with Ion</a>
</div>
</main>
{% endblock %}

View File

@ -20,11 +20,10 @@ from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('auth/', include('studyguides.apps.auth.urls', namespace='auth')),
path('', include('social_django.urls', namespace='social')),
path('', include('studyguides.apps.auth.urls', namespace='auth')),
path('', include('studyguides.apps.courses.urls', namespace='courses')),
path('', include('studyguides.apps.home.urls', namespace='home')),
path('', include('social_django.urls', namespace='social')),
]
if settings.DEBUG: