Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F9347885
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Subscribers
None
View Options
diff --git a/swh/auth/django/models.py b/swh/auth/django/models.py
index 8ff689f..59a41a7 100644
--- a/swh/auth/django/models.py
+++ b/swh/auth/django/models.py
@@ -1,86 +1,108 @@
# Copyright (C) 2020-2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
from datetime import datetime
-from typing import Optional, Set
+from typing import Any, Dict, Optional, Set
from django.contrib.auth.models import User
class OIDCUser(User):
"""
Custom User proxy model for remote users storing OpenID Connect
related data: profile containing authentication tokens.
The model is also not saved to database as all users are already stored
in the Keycloak one.
"""
# OIDC subject identifier
sub: str = ""
# OIDC tokens and session related data, only relevant when a user
# authenticates from a web browser
access_token: Optional[str] = None
+ expires_in: Optional[int] = None
expires_at: Optional[datetime] = None
id_token: Optional[str] = None
refresh_token: Optional[str] = None
+ refresh_expires_in: Optional[int] = None
refresh_expires_at: Optional[datetime] = None
scope: Optional[str] = None
session_state: Optional[str] = None
# User permissions
permissions: Set[str]
class Meta:
# TODO: To redefine in subclass of this class
# Forced to empty otherwise, django complains about it
# "Model class swh.auth.django.OIDCUser doesn't declare an explicit app_label
# and isn't in an application in INSTALLED_APPS"
app_label = ""
proxy = True
auto_created = True # prevent model to be created in database by migrations
def save(self, **kwargs):
"""
Override django.db.models.Model.save to avoid saving the remote
users to web application database.
"""
pass
def get_group_permissions(self, obj=None) -> Set[str]:
"""
Override django.contrib.auth.models.PermissionsMixin.get_group_permissions
to get permissions from OIDC
"""
return self.get_all_permissions(obj)
def get_all_permissions(self, obj=None) -> Set[str]:
"""
Override django.contrib.auth.models.PermissionsMixin.get_all_permissions
to get permissions from OIDC
"""
return self.permissions
def has_perm(self, perm, obj=None) -> bool:
"""
Override django.contrib.auth.models.PermissionsMixin.has_perm
to check permission from OIDC
"""
if self.is_active and self.is_superuser:
return True
return perm in self.permissions
def has_module_perms(self, app_label) -> bool:
"""
Override django.contrib.auth.models.PermissionsMixin.has_module_perms
to check permissions from OIDC.
"""
if self.is_active and self.is_superuser:
return True
return any(perm.startswith(app_label) for perm in self.permissions)
+
+ @property
+ def oidc_profile(self) -> Dict[str, Any]:
+ """
+ Returns OpenID Connect profile associated to the user as a dictionary.
+ """
+ return {
+ k: getattr(self, k)
+ for k in (
+ "access_token",
+ "expires_in",
+ "expires_at",
+ "id_token",
+ "refresh_token",
+ "refresh_expires_in",
+ "refresh_expires_at",
+ "scope",
+ "session_state",
+ )
+ }
diff --git a/swh/auth/tests/django/test_utils.py b/swh/auth/tests/django/test_utils.py
index 28c0f85..b90ef30 100644
--- a/swh/auth/tests/django/test_utils.py
+++ b/swh/auth/tests/django/test_utils.py
@@ -1,121 +1,118 @@
# Copyright (C) 2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
from copy import copy
from datetime import datetime
from django.test import override_settings
import pytest
from swh.auth.django.utils import (
keycloak_oidc_client,
oidc_user_from_decoded_token,
oidc_user_from_profile,
)
from swh.auth.tests.sample_data import (
CLIENT_ID,
DECODED_TOKEN,
OIDC_PROFILE,
REALM_NAME,
SERVER_URL,
)
+def _check_user(user, is_staff=False, permissions=set()):
+ assert user.id > 0
+ assert user.username == DECODED_TOKEN["preferred_username"]
+ assert user.password == ""
+ assert user.first_name == DECODED_TOKEN["given_name"]
+ assert user.last_name == DECODED_TOKEN["family_name"]
+ assert user.email == DECODED_TOKEN["email"]
+ assert user.is_staff == is_staff
+ assert user.permissions == permissions
+ assert user.sub == DECODED_TOKEN["sub"]
+
+ date_now = datetime.now()
+ if user.expires_at is not None:
+ assert isinstance(user.expires_at, datetime)
+ assert date_now <= user.expires_at
+ if user.refresh_expires_at is not None:
+ assert isinstance(user.refresh_expires_at, datetime)
+ assert date_now <= user.refresh_expires_at
+
+ assert user.oidc_profile == {
+ k: getattr(user, k)
+ for k in (
+ "access_token",
+ "expires_in",
+ "expires_at",
+ "id_token",
+ "refresh_token",
+ "refresh_expires_in",
+ "refresh_expires_at",
+ "scope",
+ "session_state",
+ )
+ }
+
+
def test_oidc_user_from_decoded_token():
user = oidc_user_from_decoded_token(DECODED_TOKEN)
-
- assert user.id == 338521271020811424925120118444075479552
- assert user.username == "johndoe"
- assert user.password == ""
- assert user.first_name == "John"
- assert user.last_name == "Doe"
- assert user.email == "john.doe@example.com"
- assert user.is_staff is False
- assert user.permissions == set()
- assert user.sub == "feacd344-b468-4a65-a236-14f61e6b7200"
+ _check_user(user)
def test_oidc_user_from_decoded_token2():
decoded_token = copy(DECODED_TOKEN)
decoded_token["groups"] = ["/staff", "api"]
decoded_token["resource_access"] = {CLIENT_ID: {"roles": ["read-api"]}}
user = oidc_user_from_decoded_token(decoded_token, client_id=CLIENT_ID)
- assert user.id == 338521271020811424925120118444075479552
- assert user.username == "johndoe"
- assert user.password == ""
- assert user.first_name == "John"
- assert user.last_name == "Doe"
- assert user.email == "john.doe@example.com"
- assert user.is_staff is True
- assert user.permissions == {"read-api"}
- assert user.sub == "feacd344-b468-4a65-a236-14f61e6b7200"
+ _check_user(user, is_staff=True, permissions={"read-api"})
@pytest.mark.parametrize(
"key,mapped_key",
[
("preferred_username", "username"),
("given_name", "first_name"),
("family_name", "last_name"),
("email", "email"),
],
)
def test_oidc_user_from_decoded_token_empty_fields_ok(key, mapped_key):
decoded_token = copy(DECODED_TOKEN)
decoded_token.pop(key, None)
user = oidc_user_from_decoded_token(decoded_token, client_id=CLIENT_ID)
- assert user.id == 338521271020811424925120118444075479552
- assert user.password == ""
- assert user.is_staff is False
- assert user.permissions == set()
- assert user.sub == "feacd344-b468-4a65-a236-14f61e6b7200"
# Ensure the missing field is mapped to an empty value
assert getattr(user, mapped_key) == ""
def test_oidc_user_from_profile(keycloak_mock):
- date_now = datetime.now()
-
user = oidc_user_from_profile(keycloak_mock, OIDC_PROFILE)
-
- assert user.id == 338521271020811424925120118444075479552
- assert user.username == "johndoe"
- assert user.password == ""
- assert user.first_name == "John"
- assert user.last_name == "Doe"
- assert user.email == "john.doe@example.com"
- assert user.is_staff is False
- assert user.permissions == set()
- assert user.sub == "feacd344-b468-4a65-a236-14f61e6b7200"
-
- assert isinstance(user.expires_at, datetime)
- assert date_now <= user.expires_at
- assert isinstance(user.refresh_expires_at, datetime)
- assert date_now <= user.refresh_expires_at
+ _check_user(user)
def test_keycloak_oidc_client_missing_django_settings():
with pytest.raises(ValueError, match="settings are mandatory"):
keycloak_oidc_client()
@override_settings(
KEYCLOAK_SERVER_URL=SERVER_URL,
KEYCLOAK_REALM_NAME=REALM_NAME,
KEYCLOAK_CLIENT_ID=CLIENT_ID,
)
def test_keycloak_oidc_client_parameters_from_django_settings(mocker):
mocker.patch("swh.auth.keycloak.KeycloakOpenID")
kc_oidc_client = keycloak_oidc_client()
assert kc_oidc_client.server_url == SERVER_URL
assert kc_oidc_client.realm_name == REALM_NAME
assert kc_oidc_client.client_id == CLIENT_ID
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Jul 4 2025, 6:00 PM (4 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3285586
Attached To
rDAUTH Common authentication libraries
Event Timeline
Log In to Comment