From 3c225f0014f8eb52020861cc4be567d610c9a2fa Mon Sep 17 00:00:00 2001 From: lauren Date: Tue, 27 Oct 2020 10:23:59 -0400 Subject: [PATCH] ion oauth --- studyguides/apps/auth/__init__.py | 1 + studyguides/apps/auth/admin.py | 3 - studyguides/apps/auth/models.py | 3 - studyguides/apps/auth/oauth.py | 44 ++++--- studyguides/apps/auth/urls.py | 4 +- studyguides/apps/auth/views.py | 14 +-- studyguides/apps/courses/urls.py | 4 +- studyguides/apps/courses/views.py | 6 +- studyguides/apps/home/urls.py | 2 +- studyguides/apps/home/views.py | 3 +- studyguides/apps/users/admin.py | 7 +- .../apps/users/migrations/0001_initial.py | 50 ++++++++ .../migrations/__init__.py} | 0 studyguides/apps/users/models.py | 112 ++++++++++++------ studyguides/apps/users/tests.py | 3 - studyguides/apps/users/urls.py | 10 -- studyguides/apps/users/views.py | 6 - studyguides/settings/__init__.py | 23 ++-- studyguides/static/login.css | 43 +++++++ studyguides/templates/login.html | 17 +++ studyguides/urls.py | 5 +- 21 files changed, 237 insertions(+), 123 deletions(-) delete mode 100644 studyguides/apps/auth/admin.py delete mode 100644 studyguides/apps/auth/models.py create mode 100644 studyguides/apps/users/migrations/0001_initial.py rename studyguides/apps/{context_processors.py => users/migrations/__init__.py} (100%) delete mode 100644 studyguides/apps/users/tests.py delete mode 100644 studyguides/apps/users/urls.py delete mode 100644 studyguides/apps/users/views.py create mode 100644 studyguides/static/login.css create mode 100644 studyguides/templates/login.html diff --git a/studyguides/apps/auth/__init__.py b/studyguides/apps/auth/__init__.py index e69de29..26c40ff 100644 --- a/studyguides/apps/auth/__init__.py +++ b/studyguides/apps/auth/__init__.py @@ -0,0 +1 @@ +default_app_config = "studyguides.apps.auth.apps.AuthConfig" diff --git a/studyguides/apps/auth/admin.py b/studyguides/apps/auth/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/studyguides/apps/auth/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/studyguides/apps/auth/models.py b/studyguides/apps/auth/models.py deleted file mode 100644 index 71a8362..0000000 --- a/studyguides/apps/auth/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/studyguides/apps/auth/oauth.py b/studyguides/apps/auth/oauth.py index eafd17b..f797ba1 100644 --- a/studyguides/apps/auth/oauth.py +++ b/studyguides/apps/auth/oauth.py @@ -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"] \ No newline at end of file diff --git a/studyguides/apps/auth/urls.py b/studyguides/apps/auth/urls.py index af37471..103f9c0 100644 --- a/studyguides/apps/auth/urls.py +++ b/studyguides/apps/auth/urls.py @@ -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"), ] diff --git a/studyguides/apps/auth/views.py b/studyguides/apps/auth/views.py index def013a..44d23ff 100644 --- a/studyguides/apps/auth/views.py +++ b/studyguides/apps/auth/views.py @@ -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") diff --git a/studyguides/apps/courses/urls.py b/studyguides/apps/courses/urls.py index 78b6085..3cace70 100644 --- a/studyguides/apps/courses/urls.py +++ b/studyguides/apps/courses/urls.py @@ -5,7 +5,7 @@ from . import views app_name = "courses" urlpatterns = [ - path("/", views.subject_view), + path("subject//", views.subject_view), path("tag//", views.tag_view, name="tag"), - path("//", views.course_view), + path("course///", views.course_view), ] diff --git a/studyguides/apps/courses/views.py b/studyguides/apps/courses/views.py index dea3d06..812cddb 100644 --- a/studyguides/apps/courses/views.py +++ b/studyguides/apps/courses/views.py @@ -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()]}) \ No newline at end of file diff --git a/studyguides/apps/home/urls.py b/studyguides/apps/home/urls.py index 1aaab90..35cad81 100644 --- a/studyguides/apps/home/urls.py +++ b/studyguides/apps/home/urls.py @@ -4,6 +4,6 @@ from . import views app_name = "home" urlpatterns = [ - path("", views.index_view, name = "index"), + path("all/", views.index_view, name="index"), ] diff --git a/studyguides/apps/home/views.py b/studyguides/apps/home/views.py index facda87..9c0b8c6 100644 --- a/studyguides/apps/home/views.py +++ b/studyguides/apps/home/views.py @@ -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()]}) diff --git a/studyguides/apps/users/admin.py b/studyguides/apps/users/admin.py index f9c7057..5c8f67d 100644 --- a/studyguides/apps/users/admin.py +++ b/studyguides/apps/users/admin.py @@ -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) diff --git a/studyguides/apps/users/migrations/0001_initial.py b/studyguides/apps/users/migrations/0001_initial.py new file mode 100644 index 0000000..ee7b1ee --- /dev/null +++ b/studyguides/apps/users/migrations/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/studyguides/apps/context_processors.py b/studyguides/apps/users/migrations/__init__.py similarity index 100% rename from studyguides/apps/context_processors.py rename to studyguides/apps/users/migrations/__init__.py diff --git a/studyguides/apps/users/models.py b/studyguides/apps/users/models.py index f3fc791..737fa0b 100644 --- a/studyguides/apps/users/models.py +++ b/studyguides/apps/users/models.py @@ -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 "".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 diff --git a/studyguides/apps/users/tests.py b/studyguides/apps/users/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/studyguides/apps/users/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/studyguides/apps/users/urls.py b/studyguides/apps/users/urls.py deleted file mode 100644 index f62712b..0000000 --- a/studyguides/apps/users/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path - -from . import views - -app_name = "users" - -urlpatterns = [ - -] - diff --git a/studyguides/apps/users/views.py b/studyguides/apps/users/views.py deleted file mode 100644 index 494a254..0000000 --- a/studyguides/apps/users/views.py +++ /dev/null @@ -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. - diff --git a/studyguides/settings/__init__.py b/studyguides/settings/__init__.py index 67c5255..166e262 100644 --- a/studyguides/settings/__init__.py +++ b/studyguides/settings/__init__.py @@ -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) diff --git a/studyguides/static/login.css b/studyguides/static/login.css new file mode 100644 index 0000000..9e0c8ee --- /dev/null +++ b/studyguides/static/login.css @@ -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; +} \ No newline at end of file diff --git a/studyguides/templates/login.html b/studyguides/templates/login.html new file mode 100644 index 0000000..fb2e1b5 --- /dev/null +++ b/studyguides/templates/login.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} + +{% load static %} + +{% block head %} + +{% endblock %} + +{% block main %} +
+

Study Guides

+ +
+{% endblock %} \ No newline at end of file diff --git a/studyguides/urls.py b/studyguides/urls.py index f70d8f6..f31774d 100644 --- a/studyguides/urls.py +++ b/studyguides/urls.py @@ -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: