Add SLO support from federated CAS
This commit is contained in:
parent
e820a3a57a
commit
6d7300fe43
@ -12,7 +12,11 @@
|
||||
from .default_settings import settings
|
||||
|
||||
from .cas import CASClient
|
||||
from .models import FederatedUser
|
||||
from .models import FederatedUser, FederateSLO, User
|
||||
|
||||
from importlib import import_module
|
||||
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
|
||||
class CASFederateValidateUser(object):
|
||||
@ -68,3 +72,33 @@ class CASFederateValidateUser(object):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def register_slo(self, username, session_key, ticket):
|
||||
FederateSLO.objects.create(
|
||||
username=username,
|
||||
session_key=session_key,
|
||||
ticket=ticket
|
||||
)
|
||||
|
||||
def clean_sessions(self, logout_request):
|
||||
try:
|
||||
SLOs = self.client.get_saml_slos(logout_request)
|
||||
except NameError:
|
||||
SLOs = []
|
||||
for slo in SLOs:
|
||||
try:
|
||||
for federate_slo in FederateSLO.objects.filter(ticket=slo.text):
|
||||
session = SessionStore(session_key=federate_slo.session_key)
|
||||
session.flush()
|
||||
try:
|
||||
user = User.objects.get(
|
||||
username=federate_slo.username,
|
||||
session_key=federate_slo.session_key
|
||||
)
|
||||
user.logout()
|
||||
user.delete()
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
federate_slo.delete()
|
||||
except FederateSLO.DoesNotExist:
|
||||
pass
|
||||
|
@ -16,6 +16,8 @@ class Command(BaseCommand):
|
||||
federated_users = models.FederatedUser.objects.filter(
|
||||
last_update__lt=(timezone.now() - timedelta(seconds=settings.CAS_TICKET_TIMEOUT))
|
||||
)
|
||||
known_users = {user.username for user in models.User.objects.all()}
|
||||
for user in federated_users:
|
||||
if not models.User.objects.filter(username='%s@%s' % (user.username, user.provider)):
|
||||
if not ('%s@%s' % (user.username, user.provider)) in known_users:
|
||||
user.delete()
|
||||
models.FederateSLO.clean_deleted_sessions()
|
||||
|
28
cas_server/migrations/0006_auto_20160623_1516.py
Normal file
28
cas_server/migrations/0006_auto_20160623_1516.py
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-06-23 15:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cas_server', '0005_auto_20160616_1018'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FederateSLO',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('username', models.CharField(max_length=30)),
|
||||
('session_key', models.CharField(blank=True, max_length=40, null=True)),
|
||||
('ticket', models.CharField(max_length=255)),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='federateslo',
|
||||
unique_together=set([('username', 'session_key')]),
|
||||
),
|
||||
]
|
@ -48,6 +48,25 @@ class FederatedUser(models.Model):
|
||||
return u"%s@%s" % (self.username, self.provider)
|
||||
|
||||
|
||||
class FederateSLO(models.Model):
|
||||
class Meta:
|
||||
unique_together = ("username", "session_key")
|
||||
username = models.CharField(max_length=30)
|
||||
session_key = models.CharField(max_length=40, blank=True, null=True)
|
||||
ticket = models.CharField(max_length=255)
|
||||
|
||||
@property
|
||||
def provider(self):
|
||||
component = self.username.split("@")
|
||||
return component[-1]
|
||||
|
||||
@classmethod
|
||||
def clean_deleted_sessions(cls):
|
||||
for federate_slo in cls.objects.all():
|
||||
if not SessionStore(session_key=federate_slo.session_key).get('authenticated'):
|
||||
federate_slo.delete()
|
||||
|
||||
|
||||
class User(models.Model):
|
||||
"""A user logged into the CAS"""
|
||||
class Meta:
|
||||
|
@ -184,6 +184,8 @@ def gen_saml_id():
|
||||
|
||||
|
||||
def get_tuple(tuple, index, default=None):
|
||||
if tuple is None:
|
||||
return default
|
||||
try:
|
||||
return tuple[index]
|
||||
except IndexError:
|
||||
|
@ -20,7 +20,7 @@ from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from django.middleware.csrf import CsrfViewMiddleware
|
||||
from django.views.generic import View
|
||||
|
||||
import logging
|
||||
@ -78,6 +78,11 @@ class LogoutMixin(object):
|
||||
username=username,
|
||||
session_key=self.request.session.session_key
|
||||
)
|
||||
if settings.CAS_FEDERATE:
|
||||
models.FederateSLO.objects.filter(
|
||||
username=username,
|
||||
session_key=self.request.session.session_key
|
||||
).delete()
|
||||
self.request.session.flush()
|
||||
user.logout(self.request)
|
||||
user.delete()
|
||||
@ -181,9 +186,33 @@ class LogoutView(View, LogoutMixin):
|
||||
|
||||
|
||||
class FederateAuth(View):
|
||||
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(FederateAuth, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_cas_client(self, request, provider):
|
||||
if provider in settings.CAS_FEDERATE_PROVIDERS:
|
||||
service_url = utils.get_current_url(request, {"ticket", "provider"})
|
||||
return CASFederateValidateUser(provider, service_url)
|
||||
|
||||
def post(self, request, provider=None):
|
||||
if not settings.CAS_FEDERATE:
|
||||
return redirect("cas_server:login")
|
||||
# POST with a provider, this is probably an SLO request
|
||||
if provider in settings.CAS_FEDERATE_PROVIDERS:
|
||||
auth = self.get_cas_client(request, provider)
|
||||
try:
|
||||
auth.clean_sessions(request.POST['logoutRequest'])
|
||||
except KeyError:
|
||||
pass
|
||||
return HttpResponse("ok")
|
||||
# else, a User is trying to log in using an identity provider
|
||||
else:
|
||||
# Manually checking for csrf to protect the code below
|
||||
reason = CsrfViewMiddleware().process_view(request, None, (), {})
|
||||
if reason is not None:
|
||||
return reason # Failed the test, stop here.
|
||||
form = forms.FederateSelect(request.POST)
|
||||
if form.is_valid():
|
||||
params = utils.copy_params(
|
||||
@ -198,7 +227,12 @@ class FederateAuth(View):
|
||||
response = HttpResponseRedirect(url)
|
||||
if form.cleaned_data["remember"]:
|
||||
max_age = settings.CAS_FEDERATE_REMEMBER_TIMEOUT
|
||||
utils.set_cookie(response, "_remember_provider", request.POST["provider"], max_age)
|
||||
utils.set_cookie(
|
||||
response,
|
||||
"_remember_provider",
|
||||
request.POST["provider"],
|
||||
max_age
|
||||
)
|
||||
return response
|
||||
else:
|
||||
return redirect("cas_server:login")
|
||||
@ -208,16 +242,17 @@ class FederateAuth(View):
|
||||
return redirect("cas_server:login")
|
||||
if provider not in settings.CAS_FEDERATE_PROVIDERS:
|
||||
return redirect("cas_server:login")
|
||||
service_url = utils.get_current_url(request, {"ticket", "provider"})
|
||||
auth = CASFederateValidateUser(provider, service_url)
|
||||
auth = self.get_cas_client(request, provider)
|
||||
if 'ticket' not in request.GET:
|
||||
return HttpResponseRedirect(auth.get_login_url())
|
||||
else:
|
||||
ticket = request.GET['ticket']
|
||||
if auth.verify_ticket(ticket):
|
||||
params = utils.copy_params(request.GET, ignore={"ticket"})
|
||||
request.session["federate_username"] = "%s@%s" % (auth.username, auth.provider)
|
||||
username = "%s@%s" % (auth.username, auth.provider)
|
||||
request.session["federate_username"] = username
|
||||
request.session["federate_ticket"] = ticket
|
||||
auth.register_slo(username, request.session.session_key, ticket)
|
||||
url = utils.reverse_params("cas_server:login", params)
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
|
Loading…
Reference in New Issue
Block a user