Skip to content

Commit

Permalink
Replace OID with Django authentication system
Browse files Browse the repository at this point in the history
Why these changes are being introduced:
* OpenID Connect (OIDC) is no longer functional and will not be supported
by ITS in the near future, and this prevents Solenoid from working.
Replacing OIDC with Django's built-in authentication system will
return the app to a functioning state until we have a better understanding
of how to apply authentication with Touchstone SAML.

How this addresses that need:
* Create a new application for authorization (accounts)
* Create a templates/accounts directory to store all templates for authorization
* Create a accounts/urls.py for creating/managing different views (pages) for authorization
* Add 'accounts' app to solenoid.settings.base.py

Note(s):

1. The structure of 'urls.py' and the templates for authorization are essentially the default files
provided by Django with minor changes. For instance, the urls.py is pretty much modeled from: https://github.com/django/django/blob/main/django/contrib/auth/urls.py.

2. The important changes to solenoid.settings.base.py (i.e., the Django configs) are essentially: (a) the addition of the 'accounts' app to SOLENOID_APPS, (b) removing references to the MITOAuth2 authentication backend and only using LOGIN_REDIRECT_URL instead, and (c) commenting out the 'WhiteNoiseMiddleWare' setting in line 76 (I'm not sure what it does, but when I compared the settings in base.py with that of the default settings, this was the only middleware that wasn't included). The other changes are formatting changes related to "Black" (i.e., line lengths and quotation marks)

Side effects of this change:
*

Relevant ticket(s):
* TBD
  • Loading branch information
jonavellecuerdo committed Nov 16, 2023
1 parent 9701163 commit 5da2d37
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 75 deletions.
37 changes: 37 additions & 0 deletions solenoid/accounts/templates/accounts/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% extends "base.html" %}

{% block content %}

{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}

{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}

<form method="post" action="{% url 'accounts:login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
</table>
<input type="submit" value="Login">
<input type="hidden" name="next" value="{{ next }}">
</form>

{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'accounts:password_reset' %}">Need help signing in?</a></p>

{% endblock %}
6 changes: 6 additions & 0 deletions solenoid/accounts/templates/accounts/logout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% extends "base.html" %}

{% block content %}
<p>You have successfully logged out. Have a nice day! </p>
<a href="{% url 'accounts:login'%}">Return to login page.</a>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% extends "base.html" %}

{% block content %}
<p>The password has been changed.</h1>
<p><a href="{% url 'accounts:login' %}">Return to login page.</a></p>
{% endblock %}
29 changes: 29 additions & 0 deletions solenoid/accounts/templates/accounts/password_reset_confirm.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{% extends "base.html" %}

{% block content %}
{% if validlink %}
<p>Please enter (and confirm) your new password.</p>
<form action="" method="post">
{% csrf_token %}
<table>
<tr>
<td>{{ form.new_password1.errors }}
<label for="id_new_password1">New password:</label></td>
<td>{{ form.new_password1 }}</td>
</tr>
<tr>
<td>{{ form.new_password2.errors }}
<label for="id_new_password2">Confirm password:</label></td>
<td>{{ form.new_password2 }}</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Change my password"></td>
</tr>
</table>
</form>
{% else %}
<h1>Password reset failed</h1>
<p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p>
{% endif %}
{% endblock %}
5 changes: 5 additions & 0 deletions solenoid/accounts/templates/accounts/password_reset_done.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{% extends "base.html" %}

{% block content %}
<p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol }}://{{ domain }}{% url 'accounts:password_reset_confirm' uidb64=uid token=token %}
13 changes: 13 additions & 0 deletions solenoid/accounts/templates/accounts/password_reset_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "base.html" %}

{% block content %}
<form action="" method="post">
{% csrf_token %}
{% if form.email.errors %}
{{ form.email.errors }}
{% endif %}
<p> Enter your email address below to receive instructions for resetting your password </p>
<p>{{ form.as_p }}</p>
<input type="submit" class="btn btn-default btn-lg" value="Reset password">
</form>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% load i18n %}{% autoescape off %}
{% blocktrans %}Password reset on {{ site_name }}{% endblocktrans %}
{% endautoescape %}
48 changes: 48 additions & 0 deletions solenoid/accounts/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django.urls import path, re_path, reverse_lazy
from django.contrib.auth.views import *
from django.views.generic import TemplateView

app_name = "accounts"

urlpatterns = [
path(
"login/",
LoginView.as_view(template_name="accounts/login.html"),
name="login",
),
path(
"logout/",
LogoutView.as_view(template_name="accounts/logout.html"),
name="logout",
),
path(
"password_reset/",
PasswordResetView.as_view(
email_template_name="accounts/password_reset_email.html",
subject_template_name="accounts/password_reset_subject.txt",
template_name="accounts/password_reset_form.html",
success_url=reverse_lazy("accounts:password_reset_done"),
),
name="password_reset",
),
path(
"password_reset/done/",
PasswordResetDoneView.as_view(template_name="accounts/password_reset_done.html"),
name="password_reset_done",
),
path(
"reset/<uidb64>/<token>/",
PasswordResetConfirmView.as_view(
template_name="accounts/password_reset_confirm.html",
success_url=reverse_lazy("accounts:password_reset_complete"),
),
name="password_reset_confirm",
),
path(
"reset/done/",
PasswordResetCompleteView.as_view(
template_name="accounts/password_reset_complete.html"
),
name="password_reset_complete",
),
]
74 changes: 5 additions & 69 deletions solenoid/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def make_list(value):
"solenoid.emails",
"solenoid.people",
"solenoid.records",
"solenoid.userauth",
"solenoid.accounts",
]

INSTALLED_APPS = DJANGO_APPS + SOLENOID_APPS
Expand All @@ -73,14 +73,14 @@ def make_list(value):
# -----------------------------------------------------------------------------

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
# 'whitenoise.middleware.WhiteNoiseMiddleware',
]


Expand Down Expand Up @@ -237,78 +237,14 @@ def make_list(value):
# -----------------> third-party and solenoid configurations <-----------------
# -----------------------------------------------------------------------------

# OAUTH CONFIGURATION
# -----------------------------------------------------------------------------

INSTALLED_APPS += ["social_django"]

# These are the people who should be allowed to log in. This should be a list
# of strings representing MIT usernames; they will be correctly formatted in
# the SOCIAL_AUTH_MITOAUTH2_WHITELISTED_EMAILS list comprehension.
WHITELIST = ["lhanscom", "khdunn", "kevgrant", "sroosa", "jprevost", "ghukill"]

SOCIAL_AUTH_MITOAUTH2_WHITELISTED_EMAILS = ["%[email protected]" % kerb for kerb in WHITELIST]

SOCIAL_AUTH_PIPELINE = [
"social_core.pipeline.social_auth.social_details",
"social_core.pipeline.social_auth.social_uid",
"social_core.pipeline.social_auth.auth_allowed",
"social_core.pipeline.social_auth.social_user",
"social_core.pipeline.social_auth.associate_by_email",
"social_core.pipeline.user.get_username",
"social_core.pipeline.user.create_user",
"social_core.pipeline.social_auth.associate_user",
]

# This is actually a token revocation pipeline, not a disconnection pipeline.
# We need to revoke the tokens, because if we don't logout appears to fail;
# it flushes users from the session, but then it checks with the social auth
# URLs, finds a token, and re-adds the user to the session, which appears to
# the user to be broken.
# It would be better to just call revoke_tokens, but this function isn't
# written to run independently of the pipeline, and it's hard to figure out
# how to hook into it.
# If we ever need to *actually* disconnect users - sever local users from
# their MIT OAuth IDs - we'll have to rework this.
SOCIAL_AUTH_DISCONNECT_PIPELINE = [
# Collects the social associations to disconnect.
"social_core.pipeline.disconnect.get_entries",
# Revoke any access_token when possible.
"social_core.pipeline.disconnect.revoke_tokens",
]

# Default to not requiring login for ease of local development, but allow it
# to be set with an environment variable to facilitate testing. You will need
# to fill in key and secret values for your environment as well if you set this
# to True.
LOGIN_REQUIRED = boolean(os.environ.get("DJANGO_LOGIN_REQUIRED", False))

if LOGIN_REQUIRED:
# args is *case-sensitive*, even though other parts of python-social-auth
# are casual about casing.
LOGIN_URL = reverse_lazy("social:begin", args=("mitoauth2",))

AUTHENTICATION_BACKENDS = [
"solenoid.userauth.backends.MITOAuth2",
# Required for user/pass authentication - this is useful for the admin
# site.
"django.contrib.auth.backends.ModelBackend",
]

SOCIAL_AUTH_MITOAUTH2_KEY = os.environ.get("DJANGO_MITOAUTH2_KEY")
SOCIAL_AUTH_MITOAUTH2_SECRET = os.environ.get("DJANGO_MITOAUTH2_SECRET")

MIDDLEWARE += [
"social_django.middleware.SocialAuthExceptionMiddleware",
]

TEMPLATES[0]["OPTIONS"]["context_processors"].extend(
[
"social_django.context_processors.backends",
"social_django.context_processors.login_redirect",
]
)

# Redirect to home URL after login (default redirects to /accounts/profile)
LOGIN_REDIRECT_URL = "/"

# CKEDITOR CONFIGURATION
# -----------------------------------------------------------------------------
Expand Down
14 changes: 8 additions & 6 deletions solenoid/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,21 @@

urlpatterns = [
re_path(r"^admin/", admin.site.urls),
re_path(
r"^accounts/",
include("solenoid.accounts.urls", namespace="accounts"),
),
re_path(r"^$", HomeView.as_view(), name="home"),
re_path(
r"^celery-progress/", include("celery_progress.urls", namespace="celery_progress")
),
re_path(r"^records/", include("solenoid.records.urls", namespace="records")),
re_path(r"^emails/", include("solenoid.emails.urls", namespace="emails")),
re_path(r"^people/", include("solenoid.people.urls", namespace="people")),
re_path(r"^oauth2/", include("social_django.urls", namespace="social")),
re_path(
r"^logout/$",
SolenoidLogoutView.as_view(template_name="userauth/logout.html"),
name="logout",
),
# re_path(r'^oauth2/', include('social_django.urls', namespace='social')),
# re_path(r'^logout/$',
# SolenoidLogoutView.as_view(template_name='userauth/logout.html'),
# name='logout'),
re_path(r"^500/$", server_error),
]

Expand Down

0 comments on commit 5da2d37

Please sign in to comment.