Skip to content
Snippets Groups Projects
Commit ae1b8dc1 authored by jan.bednarik's avatar jan.bednarik
Browse files

Abstract models for User, Team and OrgGroup

parent 81cfef61
Branches
Tags
No related merge requests found
from django.contrib import admin
from django.contrib.auth import admin as auth_admin
from django.contrib.auth import get_user_model
from .models import Team
User = get_user_model()
@admin.register(User)
class UserAdmin(auth_admin.UserAdmin):
fieldsets = (("User", {"fields": ("teams",)}),) + auth_admin.UserAdmin.fieldsets
def has_add_permission(self, request):
return False
@admin.register(Team)
class TeamAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
# TODO when auto sync is done, disable manual add
return True
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
class CustomOIDCAuthenticationBackend(OIDCAuthenticationBackend):
class PiratesOIDCAuthenticationBackend(OIDCAuthenticationBackend):
"""
Custom OIDC Authentication Backend.
Pirates OIDC Authentication Backend.
Instead of `email` uses claim `sub` (as `username`) to identify users. Which
Instead of `email` uses claim `sub` (as `sso_id`) to identify users. Which
allows for email change.
"""
def get_username(self, claims):
def get_sso_id(self, claims):
return claims.get("sub")
def filter_users_by_claims(self, claims):
username = self.get_username(claims)
if not username:
sso_id = self.get_sso_id(claims)
if not sso_id:
return self.UserModel.objects.none()
return self.UserModel.objects.filter(username=username)
return self.UserModel.objects.filter(sso_id=sso_id)
def create_user(self, claims):
username = self.get_username(claims)
sso_id = self.get_sso_id(claims)
first_name = claims.get("given_name", "")
last_name = claims.get("family_name", "")
email = claims.get("email", "")
return self.UserModel.objects.create_user(
username=username, first_name=first_name, last_name=last_name, email=email
return self.UserModel.objects.create(
sso_id=sso_id, first_name=first_name, last_name=last_name, email=email
)
def update_user(self, user, claims):
......
from django.contrib.auth.models import AbstractUser
from django.conf import settings
from django.contrib.auth.models import PermissionsMixin
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
class User(AbstractUser):
teams = models.ManyToManyField("Team")
class AbstractUser(PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
"""
def get_absolute_url(self):
return reverse("users:detail", kwargs={"username": self.username})
sso_id = models.CharField(
_("SSO ID"),
max_length=150,
unique=True,
error_messages={"unique": _("A user with that SSO ID already exists.")},
)
first_name = models.CharField(_("first name"), max_length=150, blank=True)
last_name = models.CharField(_("last name"), max_length=150, blank=True)
email = models.EmailField(_("email address"), blank=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
class Meta:
abstract = True
verbose_name = _("user")
verbose_name_plural = _("users")
def get_full_name(self):
"""Return the first_name plus the last_name, with a space in between."""
full_name = f"{self.first_name} {self.last_name}"
return full_name.strip()
def get_short_name(self):
"""Return the short name for the user."""
return self.first_name
class AbstractTeam(models.Model):
name = models.CharField(max_length=250, unique=True)
members = models.ManyToManyField(settings.AUTH_USER_MODEL)
class Meta:
abstract = True
ordering = ["name"]
verbose_name = _("team")
verbose_name_plural = _("teams")
def __str__(self):
return self.name
class Team(models.Model):
name = models.CharField(max_length=1000)
class AbstractOrgGroup(models.Model):
name = models.CharField(max_length=250, unique=True)
members = models.ManyToManyField(settings.AUTH_USER_MODEL)
class Meta:
abstract = True
ordering = ["name"]
verbose_name = _("group")
verbose_name_plural = _("groups")
def __str__(self):
return self.name
from django.contrib.auth import get_user_model
from factory import DjangoModelFactory, Faker, post_generation
from factory import DjangoModelFactory, Faker
from pirates.models import Team
from .models import OrgGroup, Team
class UserFactory(DjangoModelFactory):
username = Faker("user_name")
email = Faker("email")
@post_generation
def password(self, create, extracted, **kwargs):
password = Faker(
"password",
length=42,
special_chars=True,
digits=True,
upper_case=True,
lower_case=True,
).generate(extra_kwargs={})
self.set_password(password)
sso_id = Faker("random_letters")
class Meta:
model = get_user_model()
django_get_or_create = ["username"]
django_get_or_create = ["sso_id"]
class TeamFactory(DjangoModelFactory):
......@@ -30,3 +17,12 @@ class TeamFactory(DjangoModelFactory):
class Meta:
model = Team
django_get_or_create = ["name"]
class OrgGroupFactory(DjangoModelFactory):
name = Faker("company")
class Meta:
model = OrgGroup
django_get_or_create = ["name"]
"""
Models for tests.
"""
from pirates.models import AbstractOrgGroup, AbstractTeam, AbstractUser
class User(AbstractUser):
pass
class Team(AbstractTeam):
pass
class OrgGroup(AbstractOrgGroup):
pass
......@@ -4,9 +4,11 @@ DEBUG = True
USE_TZ = True
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache",}}
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3",}}
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}}
AUTH_USER_MODEL = "tests.User"
ROOT_URLCONF = "pirates.urls"
......@@ -17,6 +19,7 @@ INSTALLED_APPS = [
"django.contrib.sites",
"mozilla_django_oidc",
"pirates",
"tests",
]
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
......
......@@ -2,7 +2,7 @@ import pytest
from django.contrib.auth import get_user_model
from faker import Faker
from pirates.auth import CustomOIDCAuthenticationBackend
from pirates.auth import PiratesOIDCAuthenticationBackend
pytestmark = pytest.mark.django_db
......@@ -11,13 +11,13 @@ fake = Faker()
@pytest.fixture
def backend():
return CustomOIDCAuthenticationBackend()
return PiratesOIDCAuthenticationBackend()
def test_auth_backend__get_username(backend):
sub = fake.user_name()
def test_auth_backend__get_sso_id(backend):
sub = fake.random_letters()
claims = {"sub": sub}
assert backend.get_username(claims) == sub
assert backend.get_sso_id(claims) == sub
def test_auth_backend__filter_users_by_claims__missing_sub_in_claims(backend):
......@@ -27,26 +27,26 @@ def test_auth_backend__filter_users_by_claims__missing_sub_in_claims(backend):
def test_auth_backend__filter_users_by_claims__unknown_user(backend):
claims = {"sub": fake.user_name()}
claims = {"sub": fake.random_letters()}
users = backend.filter_users_by_claims(claims)
assert users.exists() is False
def test_auth_backend__filter_users_by_claims__known_user(backend, user):
claims = {"sub": user.username}
claims = {"sub": user.sso_id}
users = backend.filter_users_by_claims(claims)
assert list(users) == [user]
def test_auth_backend__create_user(backend):
claims = {
"sub": fake.user_name(),
"sub": fake.random_letters(),
"given_name": fake.first_name(),
"family_name": fake.last_name(),
"email": fake.email(),
}
user = backend.create_user(claims)
assert user.username == claims["sub"]
assert user.sso_id == claims["sub"]
assert user.first_name == claims["given_name"]
assert user.last_name == claims["family_name"]
assert user.email == claims["email"]
......@@ -55,7 +55,7 @@ def test_auth_backend__create_user(backend):
def test_auth_backend__update_user(backend, user):
claims = {
"sub": user.username,
"sub": user.sso_id,
"given_name": fake.first_name(),
"family_name": fake.last_name(),
"email": fake.email(),
......@@ -64,7 +64,7 @@ def test_auth_backend__update_user(backend, user):
assert user.last_name != claims["family_name"]
assert user.email != claims["email"]
updated_user = backend.update_user(user, claims)
assert updated_user.username == user.username
assert updated_user.sso_id == user.sso_id
assert updated_user.first_name == claims["given_name"]
assert updated_user.last_name == claims["family_name"]
assert updated_user.email == claims["email"]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment