From 516358f6edbe809c6116da5881b6f074f751562b Mon Sep 17 00:00:00 2001
From: Art Lukyanchyk <artiom.lukyanchyk@hs-hannover.de>
Date: Wed, 15 Nov 2017 15:14:15 +0100
Subject: [PATCH] Improve debug view, checks, configuration mechanisms and
default settings.
---
README.md | 32 +++++-----
ssoauth/app_settings/__init__.py | 62 ++++++++------------
ssoauth/app_settings/defaults.py | 16 ++---
ssoauth/apps.py | 17 +++---
ssoauth/auth_utils.py | 7 +--
ssoauth/checks.py | 32 +++++++++-
ssoauth/management/commands/group_mapping.py | 5 +-
ssoauth/sso_utils.py | 32 ++++++++--
ssoauth/templates/ssoauth/dev.html | 22 +++++--
ssoauth/urls.py | 3 +-
ssoauth/views.py | 54 +++++++++++++----
11 files changed, 185 insertions(+), 97 deletions(-)
diff --git a/README.md b/README.md
index d00760c..a823e51 100644
--- a/README.md
+++ b/README.md
@@ -16,31 +16,29 @@
#### Development setup
-This is what you normally want during the development.
+
+- If you don't need a fully functional SSO, you don't need to configure anything at all. Use the dev view: `localhost:8000/dev/` (might change depending on your urlconf and settings).
+
+- If you need a fully functional SSO during development, here's an example:
```python
""" settings/dev.py """
+from django import urls
import socket
import os
-DO_YOU_WANT_SSO = False
+IDP_META_URL = "https://idp-test.it.hs-hannover.de/idp/shibboleth"
-if DO_YOU_WANT_SSO:
- SP_HOST = "localhost"
- SP_PORT = 8000
- SP_SSL = False
- SP_FORCE_ENTITY_ID = "dev-auto-id-{0}-{1}".format(socket.gethostname(), os.path.dirname(os.path.dirname(__file__)))
- IDP_META_URL = "https://idp-test.it.hs-hannover.de/idp/shibboleth" # development
-else:
- SSO_DISABLED = True
-```
+SP_KEY = "{project_settings}/cert/sp.key"
+SP_CERT = "{project_settings}/cert/sp.pem"
+SP_FORCE_ENTITY_ID = "dev-auto-id-{0}-{1}".format(socket.gethostname(), os.path.dirname(os.path.dirname(__file__)))
-Use `localhost:8000/dev/` to acces the development view.
-
-The code snippet above disables the actual SSO. If you need it:
- - change `DO_YOU_WANT_SSO` to True
- - see the SSO configuration section
+SP_HOST = "localhost"
+SP_PORT = 8000
+SP_SSL = False
+LOGIN_URL = urls.reverse_lazy("sso-dev")
+```
#### Groups
To receive groups over SSO you need a mapping. You can manage group mapping with `group_mapping` management command. Example:
@@ -54,10 +52,12 @@ To receive groups over SSO you need a mapping. You can manage group mapping with
```python
""" settings/prod.py """
+from django import urls
SP_HOST = "141.71.foo.bar" # your SP host or IP address
IDP_META_URL = "https://idp.hs-hannover.de/idp/shibboleth" # production
+LOGIN_URL = urls.reverse_lazy("sso-login")
```
You will also need to configure the SSO
diff --git a/ssoauth/app_settings/__init__.py b/ssoauth/app_settings/__init__.py
index 61836c1..b043f65 100644
--- a/ssoauth/app_settings/__init__.py
+++ b/ssoauth/app_settings/__init__.py
@@ -1,7 +1,8 @@
-from onelogin.saml2 import settings as onelogin_settings
-from .defaults import *
from django import conf
from datetime import datetime, timedelta
+from onelogin.saml2 import settings as onelogin_settings
+import sys
+from .defaults import *
# merge defaults with customized user settings
@@ -15,43 +16,11 @@ for setting_name in [k for k in globals().keys() if k.isupper()]:
pass # not set
-# checks
-
-SSO_DISABLED = SSO_DISABLED or getattr(conf.settings, "IDP_IGNORE", False) # legacy config
-if SSO_DISABLED:
- assert conf.settings.DEBUG, "Not ignoring IDP on production."
-else:
- assert SP_HOST and SP_PORT, "Need SP_HOST and SP_PORT configured in settings."
- assert not SP_HOST.lower().startswith(("http:", "https:",)), "Need host name without protocol and port."
-
-# helpers
-
-_SET_ON_RUNTIME = None and "doesn't make sense to change it"
-
-
-def get_project_settings_path():
- module = os.environ.get(conf.ENVIRONMENT_VARIABLE)
- path = os.path.abspath(os.path.join(*module.split(".")))
- if not os.path.isdir(path):
- # module, not a package
- path = os.path.dirname(path)
- return path
-
-
-def read_key(path):
- path = path.format(project_settings=get_project_settings_path())
- try:
- with open(path, "r") as f:
- return f.read()
- except FileNotFoundError:
- if SSO_DISABLED:
- return None
- else:
- raise FileNotFoundError("SSO requires a key pair. Missing: {path}".format(path=path))
-
# template for OneLogin toolkit settings
+_SET_ON_RUNTIME = None and "will be set on runtime"
+
ONELOGIN_SETTINGS_TEMPLATE = {
# Don't change it (you cannot in fact)
# If you really need to adjust something here, please use ONELOGIN_OVERRIDES instead
@@ -61,9 +30,14 @@ ONELOGIN_SETTINGS_TEMPLATE = {
"entityId": _SET_ON_RUNTIME or str(),
"assertionConsumerService": {
"url": _SET_ON_RUNTIME or str(),
+ "binding": onelogin_settings.OneLogin_Saml2_Constants.BINDING_HTTP_POST,
+ },
+ "singleLogoutService": {
+ "url": _SET_ON_RUNTIME or str(),
+ "binding": onelogin_settings.OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
},
- "x509cert": read_key(SP_CERT),
- "privateKey": read_key(SP_KEY),
+ "x509cert": _SET_ON_RUNTIME,
+ "privateKey": _SET_ON_RUNTIME,
"NameIDFormat": onelogin_settings.OneLogin_Saml2_Constants.NAMEID_PERSISTENT, # otherwise Shibboleth shows warnings
},
"idp": {
@@ -102,4 +76,14 @@ ONELOGIN_SETTINGS_TEMPLATE = {
ONELOGIN_SETTINGS_TEMPLATE.update(ONELOGIN_OVERRIDES)
-ONELOGIN_SETTINGS = _SET_ON_RUNTIME
+ONELOGIN_SETTINGS_OBJECT = _SET_ON_RUNTIME
+
+
+# helpers
+
+SSO_REQUIRED = any([
+ SSO_REQUIRED_IN_DEBUG and conf.settings.DEBUG,
+ SSO_REQUIRED_IN_PRODUCTION and not conf.settings.DEBUG,
+ SSO_REQUIRED_OUTSIDE_MANAGE_PY and not any("manage.py" in arg.lower() for arg in sys.argv),
+])
+
diff --git a/ssoauth/app_settings/defaults.py b/ssoauth/app_settings/defaults.py
index b9055a8..5207242 100644
--- a/ssoauth/app_settings/defaults.py
+++ b/ssoauth/app_settings/defaults.py
@@ -13,22 +13,24 @@ Settings you may want to change:
"""
# host and port, not what Django thinks, but what nginx serves
-SP_HOST = None # e.g. "141.71.foo.bar", for development can use "localhost" and set FORCE_ENTITY_ID
+SP_HOST = "localhost" # e.g. "141.71.foo.bar", for development can use "localhost" and set FORCE_ENTITY_ID
SP_PORT = 443
SP_SSL = True
-IDP_META_URL = "https://idp.hs-hannover.de/idp/shibboleth" # test is "https://idp-test.it.hs-hannover.de/idp/shibboleth"
+IDP_META_URL = None # e.g. "https://idp-test.hs-hannover.de/idp/shibboleth"
SP_KEY = "{project_settings}/cert/sp.key"
SP_CERT = "{project_settings}/cert/sp.pem"
+SSO_REQUIRED_IN_DEBUG = False
+SSO_REQUIRED_IN_PRODUCTION = False # disabled because of e.g. collectstatic on the static server
+SSO_REQUIRED_OUTSIDE_MANAGE_PY = True # enabled to ensure that production (that uses WSGI) has SSO
+
"""
Settings you might want to change on development (don't change them for production):
"""
-# development helpers
-SSO_DISABLED = False
SP_FORCE_ENTITY_ID = None # do NOT set for production, set to some unique string on development
@@ -36,11 +38,11 @@ SP_FORCE_ENTITY_ID = None # do NOT set for production, set to some unique strin
Settings you DON'T want to change (in fact, you want to avoid even thinking about them):
"""
-SP_METADATA_LIFETIME_DAYS = 365 * 20
-
# if you really really need to add/modify something in OneLogin settings, add it to ONELOGIN_OVERRIDES
ONELOGIN_OVERRIDES = {} # e.g.: ONELOGIN_OVERRIDES = { "strict": False }
+SP_METADATA_LIFETIME_DAYS = 365 * 20
+
PROJECT_NAME = os.environ.get('DJANGO_SETTINGS_MODULE').split('.')[0]
SU_GROUP_NAME = "{0}_superusers".format(PROJECT_NAME)
@@ -50,7 +52,7 @@ STAFF_PERM_NAME = "staff"
"""
-Not settings... just... Eww.
+Not really settings...
"""
SP_CONTACTS = {
diff --git a/ssoauth/apps.py b/ssoauth/apps.py
index d78bff0..29d6e6a 100644
--- a/ssoauth/apps.py
+++ b/ssoauth/apps.py
@@ -2,6 +2,7 @@ from django.apps import AppConfig
from django.core import management
from django.db.models.signals import post_migrate
from django import conf
+import sys
from . import logger
from . import sso_utils
from . import app_settings
@@ -13,14 +14,14 @@ class SSOAuthConfig(AppConfig):
def ready(self, *args, **kwargs):
super().ready(*args, **kwargs)
# OneLogin settings stuff
- if app_settings.SSO_DISABLED:
- assert conf.settings.DEBUG
- logger.debug("SSO is disabled.")
- else:
- try:
- app_settings.ONELOGIN_SETTINGS = sso_utils.create_onelogin_settings(app_settings.ONELOGIN_SETTINGS_TEMPLATE)
- except Exception as e:
- raise RuntimeError("SSO failed to start. {ec}: {e}".format(ec=e.__class__.__name__, e=str(e),))
+ try:
+ app_settings.ONELOGIN_SETTINGS_OBJECT = sso_utils.create_onelogin_settings()
+ except Exception as e:
+ msg = "SSO failed to start. {ec}: {e}".format(ec=e.__class__.__name__, e=str(e),)
+ if app_settings.SSO_REQUIRED:
+ raise RuntimeError(msg)
+ else:
+ logger.warning(msg)
# default groups
post_migrate.connect(self.post_migrate_callback, sender=self)
diff --git a/ssoauth/auth_utils.py b/ssoauth/auth_utils.py
index c6bd5c8..98b4f79 100644
--- a/ssoauth/auth_utils.py
+++ b/ssoauth/auth_utils.py
@@ -7,7 +7,6 @@ from . import models
from . import logger
from . import app_settings
import functools
-import re
def _validate_username(username):
@@ -39,7 +38,7 @@ def get_or_create_user(uuid, username):
user = get_user(uuid=uuid)
if user.username != username:
# have to do it because users might be renamed
- logger.warning("Usernames mismatch. Changing from {0} to {1}.".format(user.username, username))
+ logger.warning("Username has changed. Renaming locally from {0} to {1}.".format(user.username, username))
user.username = username
user.save()
return user
@@ -125,11 +124,11 @@ def set_user_compat_flags(user):
group_su = Group.objects.get(name=app_settings.SU_GROUP_NAME)
group_staff = Group.objects.get(name=app_settings.STAFF_GROUP_NAME)
if group_su in user.groups.all():
- logger.info("User {user} is superuser.".format(**locals()))
+ logger.info("User {user} is superuser".format(**locals()))
user.is_staff = True
user.is_superuser = True
if group_staff in user.groups.all():
- logger.info("User {user} has admin access.".format(**locals()))
+ logger.info("User {user} is staff (has admin access)".format(**locals()))
user.is_staff = True
except Group.DoesNotExist:
logger.error("No compat groups. Migrate or run management command create_compat_groups.")
diff --git a/ssoauth/checks.py b/ssoauth/checks.py
index d7155de..991d90c 100644
--- a/ssoauth/checks.py
+++ b/ssoauth/checks.py
@@ -50,9 +50,39 @@ def compatible_user_model(app_configs, **kwargs):
return errors
+@register(Tags.compatibility)
+def host_without_protocol(app_configs, **kwargs):
+ errors = list()
+ if app_settings.SP_HOST is not None:
+ if app_settings.SP_HOST.lower().startswith(("http:", "https:",)):
+ errors.append(Error("Bad setting SP_HOST. Need host name without protocol and port."))
+ return errors
+
+
+@register(Tags.compatibility)
+def old_settings(app_configs, **kwargs):
+ def settings_have(key):
+ try:
+ getattr(conf.settings, key)
+ return True
+ except AttributeError:
+ return False
+ errors = list()
+ if settings_have("SSO_DISABLED") or settings_have("SSOAUTH_SSO_DISABLED"):
+ errors.append(Warning("SSO_DISABLED is deprecated."))
+ if settings_have("IDP_IGNORE") or settings_have("SSOAUTH_IDP_IGNORE"):
+ errors.append(Warning("IDP_IGNORE is deprecated."))
+ if errors:
+ errors.append(Warning("Please check the README. Alternatively simply remove *all* ssoauth settings from your project settings, "
+ "you don't need them anymore as long as you don't need a working SSO."))
+ return errors
+
+
@register(Tags.urls)
def auth_urls_configured(app_configs, **kwargs):
errors = list()
+ if not app_settings.SSO_REQUIRED:
+ return errors
def _url_from_setting(setting_name):
# get url based on django setting name, handles most of cases of something going wrong
@@ -61,7 +91,7 @@ def auth_urls_configured(app_configs, **kwargs):
assert urls.resolve(setting_value) # page exists
return setting_value
except Exception as e:
- errors.append(Warning("{ec}: {e}".format(ec=e.__class__.__name__, e=str(e)), obj=conf.settings,))
+ errors.append(Warning("Failed to resolve an URL for {s}. {ec}: {e}".format(ec=e.__class__.__name__, e=str(e),s=setting_name), obj=conf.settings))
return None
# login
diff --git a/ssoauth/management/commands/group_mapping.py b/ssoauth/management/commands/group_mapping.py
index 5dcb6b0..42375fa 100644
--- a/ssoauth/management/commands/group_mapping.py
+++ b/ssoauth/management/commands/group_mapping.py
@@ -94,8 +94,9 @@ class Command(BaseCommand):
return None
- # raise CommandError("Could not ensure that compatibility groups and permissions exist. {0}".format(str(e)))
-
+ ## seems like the following code can create groups and permissions as we had it
+ ## in the old hshauth, based on the project settings
+ #
# @staticmethod
# def ensure_group_exists(group_name, permission_names=list()):
# """
diff --git a/ssoauth/sso_utils.py b/ssoauth/sso_utils.py
index 7679477..d8786b7 100644
--- a/ssoauth/sso_utils.py
+++ b/ssoauth/sso_utils.py
@@ -5,6 +5,26 @@ from . import app_settings
from django import urls
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from copy import copy
+import os
+from django import conf
+
+
+def get_project_settings_path():
+ module = os.environ.get(conf.ENVIRONMENT_VARIABLE)
+ path = os.path.abspath(os.path.join(*module.split(".")))
+ if not os.path.isdir(path):
+ # module, not a package
+ path = os.path.dirname(path)
+ return path
+
+
+def read_key(path):
+ path = path.format(project_settings=get_project_settings_path())
+ try:
+ with open(path, "r") as f:
+ return f.read()
+ except FileNotFoundError:
+ raise FileNotFoundError("Could not read the key: {0}".format(path))
def get_idp_runtime_info(meta_url):
@@ -53,7 +73,7 @@ def get_idp_runtime_info(meta_url):
}
-def create_onelogin_settings(template):
+def create_onelogin_settings(template=app_settings.ONELOGIN_SETTINGS_TEMPLATE):
""" This function is intended to be run only once, on app startup. Raises exceptions. """
# get the template
template = copy(template)
@@ -62,15 +82,19 @@ def create_onelogin_settings(template):
port_suffix = "" if app_settings.SP_PORT in [80, 443] else ":{0}".format(app_settings.SP_PORT)
host = app_settings.SP_HOST
host_full = "{protocol}://{host}{port_suffix}".format(**locals())
- # SP settings
- template["sp"]["entityId"] = app_settings.SP_FORCE_ENTITY_ID or (host_full + urls.reverse("sso-saml2-meta"))
- template["sp"]["assertionConsumerService"]["url"] = host_full + urls.reverse("sso-saml2-acs")
# IDP settings
+ assert app_settings.IDP_META_URL, "IDP_META_URL is not specified" # before get_idp_runtime_info starts logging errors
idp_info = get_idp_runtime_info(app_settings.IDP_META_URL)
template["idp"]["x509certMulti"]["signing"] = idp_info["certificates"]["signing"]
template["idp"]["x509certMulti"]["encryption"] = idp_info["certificates"]["encryption"]
template["idp"]["singleSignOnService"]["url"] = idp_info["bindings"]["sso_redirect"]
template["idp"]["singleLogoutService"]["url"] = idp_info["bindings"]["slo_redirect"]
+ # SP settings
+ template["sp"]["entityId"] = app_settings.SP_FORCE_ENTITY_ID or (host_full + urls.reverse("sso-saml2-meta"))
+ template["sp"]["assertionConsumerService"]["url"] = host_full + urls.reverse("sso-saml2-acs")
+ template["sp"]["singleLogoutService"]["url"] = host_full + urls.reverse("sso-saml2-sls")
+ template["sp"]["x509cert"] = read_key(app_settings.SP_CERT)
+ template["sp"]["privateKey"] = read_key(app_settings.SP_KEY)
# done
return OneLogin_Saml2_Settings(settings=template, sp_validation_only=True)
diff --git a/ssoauth/templates/ssoauth/dev.html b/ssoauth/templates/ssoauth/dev.html
index bbf8937..cddbd57 100644
--- a/ssoauth/templates/ssoauth/dev.html
+++ b/ssoauth/templates/ssoauth/dev.html
@@ -8,6 +8,9 @@
<style>
.hidden {display: none !important;}
.wrapper>.container { padding-bottom: 2rem; padding-top: 2rem; }
+ .disabled {opacity: 0.60; filter: grayscale(100%); pointer-events: none !important;}
+ .button-narrow {padding-left: 1em; padding-right: 1em;}
+ .message {padding: 0.2em 0.5em; border: 1px solid rgba(127,127,127,0.5); border-radius: 4px; background-color: rgba(127,127,127,0.05); display: inline-block;}
</style>
</head>
<body>
@@ -17,6 +20,8 @@
<h2 class="title">Actions</h2>
<form action="" method="post">
{% csrf_token %}
+ <input type="submit" class="hidden"/> {# as chromium actually works #}
+ <button type="submit" class="hidden"></button> {# according to html5 specs #}
<div class="row">
<div class="column">
<h4 class="title">Log In</h4>
@@ -25,7 +30,7 @@
</div>
<div class="column">
<h4 class="title">Toggle Groups</h4>
- <select name="toggle_group" class="disabled" id="id_toggle_group" onchange="this.form.submit()">
+ <select name="toggle_group" id="id_toggle_group" onchange="this.form.submit()">
{% for value, name in form.fields.toggle_group.choices %}
<option value="{{ value }}">{% if value %}{{ name }}{% endif %}</option>
{% endfor %}
@@ -33,14 +38,21 @@
<p><i>Add or remove groups for the logged in user</i></p>
</div>
<div class="column">
+ <h4 class="title">Extras</h4>
+ <button type="submit" name="local-logout" value="local-logout" class="button button-outline button-narrow">Log Out</button>
+ <p><i>Extra actions for development and debugging</i></p>
+ </div>
+ <div class="column {% if not sso_configured %}disabled{% endif %}">
<h4 class="title">Production Actions</h4>
- <a class="button button-outline button-black" href="{% url "sso-login" %}?next={% url "sso-dev" %}">SSO Log in</a>
- <a class="button button-outline button-black" href="{% url "sso-logout" %}?next={% url "sso-dev" %}">Log out</a>
- <p><i>These actions are used in production</i></p>
+ <a class="button button-outline button-narrow" href="{% url "sso-login" %}?next={% url "sso-dev" %}">Log In</a>
+ <a class="button button-outline button-narrow" href="{% url "sso-logout" %}?next={% url "sso-dev" %}">Log Out</a>
+ <a class="button button-outline button-narrow" href="{% url "sso-saml2-meta" %}" target="_blank">Meta</a>
+ <p><i>{% if sso_configured %}These actions are used in production{% else %}Your SSO settings are a potato{% endif %}</i></p>
</div>
</div>
- <input class="hidden" type="submit">
</form>
+ {% if next_url %}<p class="message">You will be redirected to: <strong>{{ next_url }}</strong></p>{% endif %}
+ {% if user.is_superuser %}<p class="message">You are a superuser</p>{% endif %}
</section>
{% for table_name, table_contents in tables %}
diff --git a/ssoauth/urls.py b/ssoauth/urls.py
index f63ebf5..9048579 100644
--- a/ssoauth/urls.py
+++ b/ssoauth/urls.py
@@ -5,7 +5,8 @@ urlpatterns = (
url(r"^login/?$", views.LogInView.as_view(), name="sso-login"),
url(r"^logout/?$", views.LogOutView.as_view(), name="sso-logout"),
url(r"^saml2/acs/?$", views.ACSAuthNView.as_view(), name="sso-saml2-acs"),
- url(r"^saml2/meta/?$", views.MetadataView.as_view(), name="sso-saml2-meta"),
+ url(r"^saml2/sls/?$", views.SLSView.as_view(), name="sso-saml2-sls"),
+ url(r"^saml2/meta(?:data)?/?$", views.MetadataView.as_view(), name="sso-saml2-meta"),
url(r"^dev/?$", views.DevView.as_view(), name="sso-dev"),
)
diff --git a/ssoauth/views.py b/ssoauth/views.py
index c100631..07e8adc 100644
--- a/ssoauth/views.py
+++ b/ssoauth/views.py
@@ -32,7 +32,7 @@ class SAMLMixin:
""" Merges Django runtime info and settings for OneLogin toolkit """
def __init__(self, *args, **kwargs):
- if not app_settings.ONELOGIN_SETTINGS:
+ if not app_settings.ONELOGIN_SETTINGS_OBJECT:
raise exceptions.ImproperlyConfigured("SSO is not configured.")
super().__init__(*args, **kwargs)
@@ -52,7 +52,7 @@ class SAMLMixin:
def get_onelogin_auth(cls, request):
return OneLogin_Saml2_Auth(
request_data=cls.get_onelogin_request_data(request),
- old_settings=app_settings.ONELOGIN_SETTINGS
+ old_settings=app_settings.ONELOGIN_SETTINGS_OBJECT
)
@@ -148,11 +148,22 @@ class ACSAuthNView(SAMLMixin, View):
logger.debug("Logged in {user}".format(**locals()))
+@method_decorator(never_cache, "dispatch")
+@method_decorator(csrf_exempt, "dispatch")
+class SLSView(SAMLMixin, View):
+ """
+ SLS (Single Logout Service) binding.
+ When this view is opened by a user (usually inside of a frame) this user must be unconditionally logged off.
+ """
+ def get(self):
+ return None
+
+
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)
+ meta = app_settings.ONELOGIN_SETTINGS_OBJECT.get_sp_metadata()
+ errors = app_settings.ONELOGIN_SETTINGS_OBJECT.validate_metadata(meta)
if errors:
for e in errors:
logger.error(e)
@@ -178,34 +189,51 @@ class DevView(FormView):
else:
super().__init__(*args, **kwargs)
+ @property
+ def next_url(self):
+ return self.request.GET.get(REDIRECT_FIELD_NAME, None)
+
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()
+ permissions = list(user.get_all_permissions())
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)],
+ ["permissions", ", ".join(str(p) for p in permissions)],
])],
["SAML2 Attributes", self.request.session.get("DEBUG_SAML2_ATTRS")],
["Session", dict(self.request.session)],
]
+ context.update(dict(
+ sso_configured=bool(app_settings.ONELOGIN_SETTINGS_OBJECT),
+ next_url=self.next_url,
+ ))
return context
def form_valid(self, form):
assert conf.settings.DEBUG
- username = form.cleaned_data["username"]
+ log_in_as_username = form.cleaned_data["username"]
toggle_group = form.cleaned_data["toggle_group"]
- if username:
+ local_logout = bool(self.request.POST.get("local-logout", None))
+ if local_logout:
+ if log_in_as_username or toggle_group:
+ log_in_as_username, toggle_group = None, None # single page / single form for everything can cause weird effects
+ # perform an action
+ if log_in_as_username:
+ logger.info("Logging in as {0}".format(log_in_as_username))
try:
- user = auth_utils.get_user(username=username)
+ user = auth_utils.get_user(username=log_in_as_username)
except exceptions.ObjectDoesNotExist:
import uuid
- user = auth_utils.get_or_create_user(username=username, uuid=uuid.uuid4())
+ user = auth_utils.get_or_create_user(username=log_in_as_username, uuid=uuid.uuid4())
self.request.user = user
contrib_auth.login(request=self.request, user=user)
elif toggle_group:
+ logger.info("Toggling group: {0}".format(toggle_group))
if self.request.user.is_authenticated:
if toggle_group in self.request.user.groups.all():
self.request.user.groups.remove(toggle_group)
@@ -213,6 +241,12 @@ class DevView(FormView):
self.request.user.groups.add(toggle_group)
else:
logger.warning("Too anonymous to join groups.")
- auth_utils.set_user_compat_flags(self.request.user)
- return http.HttpResponseRedirect(urls.reverse("sso-dev"))
+ elif local_logout:
+ logger.info("Logging out")
+ contrib_auth.logout(self.request)
+ # update the compat flags, might be needed when user or their groups change
+ if self.request.user.is_authenticated:
+ auth_utils.set_user_compat_flags(self.request.user)
+ # redirect
+ return http.HttpResponseRedirect(self.next_url or urls.reverse("sso-dev"))
--
GitLab