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

Use a generic unique ID attribute instead of UUID

parent ac686a9f
No related branches found
No related tags found
No related merge requests found
...@@ -58,6 +58,14 @@ PREDEFINED_GROUPS = { ...@@ -58,6 +58,14 @@ PREDEFINED_GROUPS = {
CLEANUP_DEACTIVATE_AFTER = timedelta(days=7) # people are getting suspicious because of the old users that still seem active according to django CLEANUP_DEACTIVATE_AFTER = timedelta(days=7) # people are getting suspicious because of the old users that still seem active according to django
CLEANUP_DELETE_USER_AFTER = timedelta(days=180) CLEANUP_DELETE_USER_AFTER = timedelta(days=180)
# the block below defines from which SAML2 attributes this SP receives user data
ATTRIBUTE_USERNAME = "urn:oid:2.5.4.3" # cn (alternatively for example uid "urn:oid:0.9.2342.19200300.100.1.1")
ATTRIBUTE_EMAIL = "urn:oid:0.9.2342.19200300.100.1.3" # "mail"
ATTRIBUTE_FORENAME = "urn:oid:2.5.4.42" # "givenName"
ATTRIBUTE_SURNAME = "urn:oid:2.5.4.4" # "sn"
ATTRIBUTE_UNIQUE_ID = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6" # "eduPersonPrincipalName" (expected to be permanent)
ATTRIBUTE_GROUPS = "ssoGroup" # HsH custom attribute (alternatively something like "urn:oid:1.3.6.1.4.1.5923.1.5.1.1" which is "isMemberOf")
""" """
Settings you might want to change on development (don't change them for production): Settings you might want to change on development (don't change them for production):
...@@ -79,14 +87,6 @@ PROJECT_NAME = os.environ.get('DJANGO_SETTINGS_MODULE').split('.')[0] ...@@ -79,14 +87,6 @@ PROJECT_NAME = os.environ.get('DJANGO_SETTINGS_MODULE').split('.')[0]
PRETEND_AUTH_BACKEND = django_settings.AUTHENTICATION_BACKENDS[0] # pretend to be this backend; django does not expect that it is possible to log in without an authentication backend PRETEND_AUTH_BACKEND = django_settings.AUTHENTICATION_BACKENDS[0] # pretend to be this backend; django does not expect that it is possible to log in without an authentication backend
# the block below defines from which SAML2 attributes this SP receives user data
ATTRIBUTE_USERNAME = "urn:oid:0.9.2342.19200300.100.1.1" # "uid"
ATTRIBUTE_EMAIL = "urn:oid:0.9.2342.19200300.100.1.3" # "mail"
ATTRIBUTE_FORENAME = "urn:oid:2.5.4.42" # "givenName"
ATTRIBUTE_SURNAME = "urn:oid:2.5.4.4" # "sn"
ATTRIBUTE_ACCOUNT_UUID = "UUID" # custom stuff, this one has no OID
ATTRIBUTE_GROUPS = "urn:oid:1.3.6.1.4.1.5923.1.5.1.1" # "isMemberOf"
GROUP_RESOLVER = None # deprecated GROUP_RESOLVER = None # deprecated
""" """
......
from uuid import UUID, uuid4 import secrets
from django.db import transaction from django.db import transaction
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
...@@ -16,19 +16,17 @@ def _validate_username(username): ...@@ -16,19 +16,17 @@ def _validate_username(username):
raise ValueError("Username must be lowere case") raise ValueError("Username must be lowere case")
def get_user(uuid=None, username=None): def get_user(unique_id=None, username=None):
""" This helper just gets a user instance. """ """ This helper just gets a user instance. """
assert bool(uuid) ^ bool(username), "Need uuid OR username" assert bool(unique_id) ^ bool(username), "Need unique_id OR username"
if uuid: if unique_id:
assert isinstance(uuid, UUID), "Not an UUID instance" return models.UserMapping.objects.get(unique_id=unique_id).user
return models.UserMapping.objects.get(uuid=uuid).user
elif username: elif username:
assert isinstance(username, str), "Seriously?"
return get_user_model().objects.get(username__iexact=username.lower()) return get_user_model().objects.get(username__iexact=username.lower())
@transaction.atomic() @transaction.atomic()
def get_or_create_user(uuid, username): def get_or_create_user(unique_id, username):
""" """
Returns a user. Returns a user.
Should be able to process a bunch of weird cases like changed username or duplicate usernames. Should be able to process a bunch of weird cases like changed username or duplicate usernames.
...@@ -40,14 +38,14 @@ def get_or_create_user(uuid, username): ...@@ -40,14 +38,14 @@ def get_or_create_user(uuid, username):
other_user = get_user(username=username) other_user = get_user(username=username)
except get_user_model().DoesNotExist: except get_user_model().DoesNotExist:
return return
new_username = "{0}_OLD_{1}".format(other_user.username, uuid4()) new_username = "{0}_OLD_{1}".format(other_user.username, secrets.token_hex())
logger.warning("Found another user with username {old_username}. Renaming {old_username} to {new_username}".format(old_username=other_user.username, new_username=new_username)) logger.warning("Found another user with username {old_username}. Renaming {old_username} to {new_username}".format(old_username=other_user.username, new_username=new_username))
other_user.username = new_username other_user.username = new_username
other_user.save() other_user.save()
def get_existing_user(uuid, username): def get_existing_user(unique_id, username):
try: try:
user = get_user(uuid=uuid) user = get_user(unique_id=unique_id)
if user.username != username: if user.username != username:
free_up_username(username) free_up_username(username)
logger.warning("Username has changed. Renaming locally from {0} to {1}.".format(user.username, username)) logger.warning("Username has changed. Renaming locally from {0} to {1}.".format(user.username, username))
...@@ -57,23 +55,21 @@ def get_or_create_user(uuid, username): ...@@ -57,23 +55,21 @@ def get_or_create_user(uuid, username):
except models.UserMapping.DoesNotExist: except models.UserMapping.DoesNotExist:
return None return None
def create_user(uuid, username): def create_user(unique_id, username):
_validate_username(username) _validate_username(username)
free_up_username(username) free_up_username(username)
user = get_user_model().objects.create(username=username, is_staff=False) user = get_user_model().objects.create(username=username, is_staff=False)
user.set_unusable_password() user.set_unusable_password()
user.save() user.save()
models.UserMapping.objects.create(user=user, uuid=uuid) models.UserMapping.objects.create(user=user, unique_id=unique_id)
logger.info("Created user: {username} {uuid}".format(**locals())) logger.info("Created user: {username} {unique_id}".format(**locals()))
return user return user
# prepare # prepare
if isinstance(uuid, str): assert isinstance(unique_id, str) and isinstance(username, str)
uuid = UUID(uuid)
assert isinstance(uuid, UUID) and isinstance(username, str)
username = username.lower() username = username.lower()
# get or create # get or create
user = get_existing_user(uuid, username) or create_user(uuid, username) user = get_existing_user(unique_id, username) or create_user(unique_id, username)
# just in case, ensure the user object complies with the security rules # just in case, ensure the user object complies with the security rules
cleanup_direct_permissions(user) cleanup_direct_permissions(user)
return user return user
......
# Generated by Django 4.2.7 on 2024-03-18 16:11
from django.db import migrations, models
import secrets
class Migration(migrations.Migration):
dependencies = [
('ssoauth', '0002_auto_20190306_1823'),
]
operations = [
migrations.RemoveField(
model_name='usermapping',
name='uuid',
),
migrations.AddField(
model_name='usermapping',
name='unique_id',
field=models.TextField(default=secrets.token_hex),
),
]
import secrets
from django.db import models from django.db import models
from django import conf from django import conf
class UserMapping(models.Model): class UserMapping(models.Model):
user = models.OneToOneField(conf.settings.AUTH_USER_MODEL, primary_key=True, on_delete=models.CASCADE, related_name="sso_mapping") user = models.OneToOneField(conf.settings.AUTH_USER_MODEL, primary_key=True, on_delete=models.CASCADE, related_name="sso_mapping")
uuid = models.UUIDField(null=False) unique_id = models.TextField(null=False, default=secrets.token_hex)
anonymized = models.BooleanField(null=False, default=False) anonymized = models.BooleanField(null=False, default=False)
imported_on = models.DateField(null=False, auto_now_add=True) imported_on = models.DateField(null=False, auto_now_add=True)
def __str__(self): def __str__(self):
return "{user} <-> {uuid}".format(user=self.user, uuid=self.uuid) return "{user} <-> {unique_id}".format(user=self.user, unique_id=self.unique_id)
import secrets
from django.views.generic import View, FormView, TemplateView, RedirectView from django.views.generic import View, FormView, TemplateView, RedirectView
from django import http from django import http
from django import urls from django import urls
...@@ -192,7 +193,7 @@ class ACSAuthNView(SAMLMixin, View): ...@@ -192,7 +193,7 @@ class ACSAuthNView(SAMLMixin, View):
logger.debug("Synchronizing user using SAML2 data: {}".format(auth.get_attributes())) logger.debug("Synchronizing user using SAML2 data: {}".format(auth.get_attributes()))
# get the user # get the user
user = auth_utils.get_or_create_user( user = auth_utils.get_or_create_user(
uuid=get_attr(app_settings.ATTRIBUTE_ACCOUNT_UUID), unique_id=get_attr(app_settings.ATTRIBUTE_UNIQUE_ID),
username=get_attr(app_settings.ATTRIBUTE_USERNAME), username=get_attr(app_settings.ATTRIBUTE_USERNAME),
) )
# update user data # update user data
...@@ -338,8 +339,7 @@ class DevView(FormView): ...@@ -338,8 +339,7 @@ class DevView(FormView):
try: try:
user = auth_utils.get_user(username=log_in_as_username) user = auth_utils.get_user(username=log_in_as_username)
except exceptions.ObjectDoesNotExist: except exceptions.ObjectDoesNotExist:
import uuid user = auth_utils.get_or_create_user(username=log_in_as_username, unique_id=secrets.token_hex())
user = auth_utils.get_or_create_user(username=log_in_as_username, uuid=uuid.uuid4())
user.backend = app_settings.PRETEND_AUTH_BACKEND user.backend = app_settings.PRETEND_AUTH_BACKEND
self.request.user = user self.request.user = user
contrib_auth.login(request=self.request, user=user) contrib_auth.login(request=self.request, user=user)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment