Page MenuHomeSoftware Heritage

No OneTemporary

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

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

Event Timeline