Maintenance will be performed on,,, and on Tuesday 22nd September 2020 starting at approximately 9am MST.It is expected to take around 15 minutes and there will be a short period of downtime towards the end of the maintenance window. Please address any comments, questions, or concerns to

Commit 1e68eb7a authored by Tanner Prestegard's avatar Tanner Prestegard Committed by GraceDB

ligoauth: rework login and post-login views

parent 2c87e79a
......@@ -11,7 +11,7 @@ from events.feeds import EventFeed, feedview
import events.reports
import events.views
from ligoauth.views import (
pre_login, post_login, manage_password
manage_password, ShibLoginView, ShibPostLoginView
import search.views
......@@ -51,8 +51,8 @@ urlpatterns = [
url(r'^search/$',, name="mainsearch"),
# Authentication
url(r'^login/$', pre_login, name='login'),
url(r'^post-login/$', post_login, name='post-login'),
url(r'^login/$', ShibLoginView.as_view(), name='login'),
url(r'^post-login/$', ShibPostLoginView.as_view(), name='post-login'),
url(r'^logout/$', LogoutView.as_view(), name='logout'),
# Password management
import logging
from django.conf import settings
from django.contrib.auth import get_user_model, update_session_auth_hash
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.contrib.auth import (
get_user_model, update_session_auth_hash, REDIRECT_FIELD_NAME
from django.contrib.auth.views import SuccessURLAllowedHostsMixin
from django.http import HttpResponseRedirect
from django.shortcuts import resolve_url, render
from django.urls import reverse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.http import urlencode, is_safe_url
from django.views.decorators.cache import never_cache
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import RedirectView
from .decorators import lvem_observers_only
......@@ -17,69 +25,76 @@ UserModel = get_user_model()
logger = logging.getLogger(__name__)
ORIGINAL_PAGE_KEY = 'login_from_page'
# Three steps in login process:
# 1. Pre-login view where we try to cache the page that the user was just on
# and redirect to the Shibboleth SSO page for login through an IdP
# 1. Pre-login view where we set up all of the necessary redirects, starting
# with the Shibboleth SSO page for login through an IdP.
# 2. Login through IdP, redirect to post-login view.
# 3. Post-login view, where Apache puts the user's attributes into the
# session. Our Django middleware and auth backends consume the attributes
# and use them to log into a user account in the database. The user is
# then redirected to the original page where they logged in from.
def pre_login(request):
Sends user to settings.SHIB_LOGIN_URL (Shibboleth login) and sets up a
redirect target to the actual login page where we parse the shib session
attributes. Saves the current page (where the login button was clicked
from) in the session so that our login page can then redirect back to
the original page.
If original URL is not found, redirect to the home page
# Set target for SSO page to redirect to
shib_target = reverse('post-login')
# Get original url (page where the login button was clicked).
# First try to get referer header. If not available, try to get the 'next
# query string parameter (that's how the Django login_required
# handles it)
original_url = request.META.get('HTTP_REFERER', None)
if original_url is None:
original_url = request.GET.get('next',
# Store original url in session
request.session[ORIGINAL_PAGE_KEY] = original_url
# Set up url for shibboleth login with redirect target
full_login_url = "{base}?target={target}".format(
base=settings.SHIB_LOGIN_URL, target=shib_target)
# Redirect to the shibboleth login
return HttpResponseRedirect(full_login_url)
def post_login(request):
pre_login should redirect to the URL which corresponds to this view.
Apache should be configured to put the Shibboleth session information into
the request headers at this view's URL.
The middleware should handle attribute extraction and logging in. So all
we need to do here is redirect to the original page (where the user clicked
the login button). If we can't seem to find that information, then just
redirect to the home page.
original_url = request.session.get(ORIGINAL_PAGE_KEY,
# Redirect to the original url
return HttpResponseRedirect(original_url)
# request headers. Our Django middleware and auth backends consume the
# attributes and use them to log into a user account in the database. The
# user is then redirected to the original page where they logged in from.
@method_decorator(sensitive_post_parameters(), name='dispatch')
@method_decorator(never_cache, name='dispatch')
class ShibLoginView(SuccessURLAllowedHostsMixin, RedirectView):
redirect_authenticated_user = True
redirect_field_name = REDIRECT_FIELD_NAME
post_login_view_name = 'post-login'
def dispatch(self, request, *args, **kwargs):
if (self.redirect_authenticated_user
and self.request.user.is_authenticated):
redirect_to = self.get_success_url()
if redirect_to == self.request.path:
raise ValueError(
"Redirection loop for authenticated user detected. Check "
"that your LOGIN_REDIRECT_URL doesn't point to a login "
return HttpResponseRedirect(redirect_to)
return super(ShibLoginView, self).dispatch(request, *args, **kwargs)
def get_success_url(self):
"""User is already logged in."""
url = self.request.POST.get(
return url
def get_redirect_url(self, *args, **kwargs):
# Where the user should finally be redirected to
original_url = self.get_success_url()
# Target to pass to the shibboleth SSO page as a URL param
shib_target = "{url}?{params}".format(
params=urlencode({self.redirect_field_name: original_url})
# Full URL to redirect to right now
full_login_url = "{base}?{params}".format(
params=urlencode({'target': shib_target})
return full_login_url
@method_decorator(sensitive_post_parameters(), name='dispatch')
@method_decorator(never_cache, name='dispatch')
class ShibPostLoginView(SuccessURLAllowedHostsMixin, RedirectView):
redirect_field_name = REDIRECT_FIELD_NAME
def get_redirect_url(self):
redirect_to = self.request.GET.get(self.redirect_field_name, '')
url_is_safe = is_safe_url(
return redirect_to if url_is_safe else ''
......@@ -29,7 +29,7 @@
{% endif %}
{% else %}
<li id="nav-login"><a href="{% url "login" %}">Login</a></li>
<li id="nav-login"><a href="{% url "login" %}?next={{ request.path }}">Login</a></li>
{% endif %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment