Skip to content
Snippets Groups Projects
Commit 135adc0f authored by Art's avatar Art :lizard:
Browse files

RC1.

parent 2aa97ee0
Branches
No related tags found
No related merge requests found
from django.views.generic import View, FormView
from django import http
from django import urls
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.contrib import auth as contrib_auth
from django.contrib.auth import models as contrib_auth_models
from django import conf
from django.core import exceptions
from django import forms
from . import logger
from . import app_settings
from . import auth_utils
from onelogin.saml2.utils import OneLogin_Saml2_Utils
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from collections import OrderedDict
ATTRIBUTE_MAPPING = dict(
# https://commons.lbl.gov/display/IDMgmt/Attribute+Definitions
username="urn:oid:0.9.2342.19200300.100.1.1",
email="urn:oid:0.9.2342.19200300.100.1.3",
forename="urn:oid:2.5.4.42",
surname="urn:oid:2.5.4.4",
uuid="UUID",
idm_groups="IDMGroups",
)
class SAMLMixin:
""" Merges Django runtime info and settings for OneLogin toolkit """
@staticmethod
def get_onelogin_request_data(request):
return {
# since nginx is in the way, the settings cannot be fetched from the request
'https': 'on', # nginx should do it and django will never have a clue
'http_host': app_settings.SP_HOST, # nginx might change it
'server_port': app_settings.SP_PORT, # nginx will change it
'script_name': request.META['PATH_INFO'],
'get_data': request.GET.copy(),
'post_data': request.POST.copy(),
}
@classmethod
def get_onelogin_auth(cls, request):
return OneLogin_Saml2_Auth(
request_data=cls.get_onelogin_request_data(request),
old_settings=app_settings.ONELOGIN_SETTINGS
)
class LogInView(SAMLMixin, View):
def get(self, request, *args, **kwargs):
next_url = "{host}{relative}".format(
host=OneLogin_Saml2_Utils.get_self_url(self.get_onelogin_request_data(request)),
relative=urls.reverse("sso-saml2-meta"),
)
auth = self.get_onelogin_auth(request)
login = auth.login(return_to=next_url)
return http.HttpResponseRedirect(login)
class LogOutView(View):
def get(self, request, *args, **kwargs):
contrib_auth.logout(request)
logger.warning("Don't know what to do after logging out in Django.")
return http.HttpResponseRedirect(urls.reverse("sso-dev"))
@method_decorator(csrf_exempt, "dispatch")
class ACSAuthNView(SAMLMixin, View):
"""
This is NOT a universal ACS. It can only consume artifacts with an AuthN statement.
It's how OneLogin toolkit works, cannot easily detect/process other statements here, so I don't even try.
"""
def post(self, request, *args, **kwargs):
auth = self.get_onelogin_auth(request)
auth.process_response()
if auth.is_authenticated():
self.log_in_user(request, auth)
if conf.settings.DEBUG:
request.session["DEBUG_SAML2_ATTRS"] = auth.get_attributes()
return http.HttpResponseRedirect(urls.reverse("sso-dev"))
else:
logger.error("Not authenticated. Errors: {0}".format(auth.get_errors()))
raise exceptions.PermissionDenied()
def log_in_user(self, request, auth):
def get_attr(attribute_name, nullable=False, multivalued=False):
attribute_id = ATTRIBUTE_MAPPING[attribute_name]
values = auth.get_attributes().get(attribute_id, list())
assert nullable or values, "Haven't received any {0}".format(attribute_name)
if multivalued:
return values
else:
assert len(values) <= 1, "Received too many {0}: {1}".format(attribute_name, values)
return values[0] if values else None
logger.debug("Synchronizing user using SAML2 data: {}".format(auth.get_attributes()))
# get the user
user = auth_utils.get_or_create_user(
uuid=get_attr("uuid"),
username=get_attr("username"),
)
# update user data
auth_utils.update_user_data(
user=user,
surname=get_attr("surname", nullable=True),
forename=get_attr("forename", nullable=True),
email=get_attr("email", nullable=True),
)
auth_utils.set_user_groups(
user=user,
group_dn_list=get_attr("idm_groups", nullable=True, multivalued=True) or list()
)
auth_utils.set_user_compat_flags(user=user)
request.user = user
contrib_auth.login(request, user)
logger.debug("Logged in {user}".format(**locals()))
class MetadataView(SAMLMixin, View):
def get(self, request, *args, **kwargs):
meta = app_settings.ONELOGIN_SETTINGS.get_sp_metadata()
errors = app_settings.ONELOGIN_SETTINGS.validate_metadata(meta)
if errors:
for e in errors:
logger.error(e)
return http.HttpResponseServerError()
return http.HttpResponse(content_type="text/xml", content=meta)
class DevView(FormView):
class DevForm(forms.Form):
username = forms.CharField(required=False)
toggle_group = forms.ModelChoiceField(
queryset=contrib_auth_models.Group.objects.all(),
required=False,
)
template_name = "ssoauth/dev.html"
form_class = DevForm
def __init__(self, *args, **kwargs):
if not conf.settings.DEBUG and False:
raise exceptions.PermissionDenied()
else:
super().__init__(*args, **kwargs)
def get_context_data(self, **kwargs):
assert conf.settings.DEBUG
context = super().get_context_data(**kwargs)
user = self.request.user
groups = list(user.groups.all()) if user.is_authenticated else list()
context["tables"] =[
["User", OrderedDict([
["user", "{0} ({1})".format(self.request.user.username, self.request.user.__class__.__name__)],
["groups", ", ".join(str(g) for g in groups)],
])],
["SAML2 Attributes", self.request.session.get("DEBUG_SAML2_ATTRS")],
["Session", dict(self.request.session)],
]
return context
def form_valid(self, form):
assert conf.settings.DEBUG
username = form.cleaned_data["username"]
toggle_group = form.cleaned_data["toggle_group"]
if username:
try:
user = auth_utils.get_user(username=username)
except exceptions.ObjectDoesNotExist:
import uuid
user = auth_utils.get_or_create_user(username=username, uuid=uuid.uuid4())
self.request.user = user
contrib_auth.login(request=self.request, user=user)
elif toggle_group:
if self.request.user.is_authenticated:
if toggle_group in self.request.user.groups.all():
self.request.user.groups.remove(toggle_group)
else:
self.request.user.groups.add(toggle_group)
else:
logger.warning("Too anonymous to join groups.")
return http.HttpResponseRedirect(urls.reverse("sso-dev"))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment