Skip to content
Snippets Groups Projects
Commit f4a4dcfe authored by Jenkins for Software Heritage's avatar Jenkins for Software Heritage
Browse files

Update upstream source from tag 'debian/upstream/0.3.0'

Update to upstream version '0.3.0'
with Debian dir 60fc60264a50eec818fa87bdb165ec7fa83d18c3
parents 57957f53 27fd9275
No related branches found
Tags swh-loader-highpriority-20230203.1
No related merge requests found
Showing
with 293 additions and 4 deletions
Metadata-Version: 2.1
Name: swh.auth
Version: 0.2.0
Version: 0.3.0
Summary: Software Heritage Authentication Utilities
Home-page: https://forge.softwareheritage.org/source/swh-auth/
Author: Software Heritage developers
......@@ -24,4 +24,5 @@ Classifier: Operating System :: OS Independent
Classifier: Development Status :: 3 - Alpha
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Provides-Extra: django
Provides-Extra: testing
......@@ -13,3 +13,6 @@ ignore_missing_imports = True
[mypy-keycloak.*]
ignore_missing_imports = True
[mypy-django.*]
ignore_missing_imports = True
[pytest]
norecursedirs = docs .*
DJANGO_SETTINGS_MODULE = swh.auth.tests.app.apptest.settings
Django<3
pytest
requests_mock
pytest-django
......@@ -50,7 +50,10 @@ setup(
tests_require=parse_requirements("test"),
setup_requires=["setuptools-scm"],
use_scm_version=True,
extras_require={"testing": parse_requirements("test")},
extras_require={
"django": parse_requirements("django"),
"testing": parse_requirements("test"),
},
include_package_data=True,
# entry_points="""
# [swh.cli.subcommands]
......
Metadata-Version: 2.1
Name: swh.auth
Version: 0.2.0
Version: 0.3.0
Summary: Software Heritage Authentication Utilities
Home-page: https://forge.softwareheritage.org/source/swh-auth/
Author: Software Heritage developers
......@@ -24,4 +24,5 @@ Classifier: Operating System :: OS Independent
Classifier: Development Status :: 3 - Alpha
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Provides-Extra: django
Provides-Extra: testing
......@@ -11,6 +11,7 @@ conftest.py
mypy.ini
pyproject.toml
pytest.ini
requirements-django.txt
requirements-swh.txt
requirements-test.txt
requirements.txt
......@@ -34,6 +35,19 @@ swh/auth/cli.py
swh/auth/keycloak.py
swh/auth/py.typed
swh/auth/pytest_plugin.py
swh/auth/django/__init__.py
swh/auth/django/models.py
swh/auth/django/utils.py
swh/auth/tests/__init__.py
swh/auth/tests/conftest.py
swh/auth/tests/sample_data.py
swh/auth/tests/test_keycloak.py
\ No newline at end of file
swh/auth/tests/test_keycloak.py
swh/auth/tests/test_models.py
swh/auth/tests/test_utils.py
swh/auth/tests/app/__init__.py
swh/auth/tests/app/manage.py
swh/auth/tests/app/apptest/__init__.py
swh/auth/tests/app/apptest/apps.py
swh/auth/tests/app/apptest/models.py
swh/auth/tests/app/apptest/settings.py
swh/auth/tests/app/apptest/urls.py
\ No newline at end of file
python-keycloak>=0.19.0
swh.core[http]>=0.3
[django]
Django<3
[testing]
pytest
requests_mock
pytest-django
# 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 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_at: Optional[datetime] = None
id_token: Optional[str] = None
refresh_token: Optional[str] = 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
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)
# 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, timedelta
from typing import Any, Dict, Optional
from swh.auth.django.models import OIDCUser
from swh.auth.keycloak import KeycloakOpenIDConnect
def oidc_user_from_decoded_token(
decoded_token: Dict[str, Any], client_id: Optional[str] = None
) -> OIDCUser:
"""Create an OIDCUser out of a decoded token
Args:
decoded_token: Decoded token Dict
client_id: Optional client id of the keycloak client instance used to decode
the token. If not provided, the permissions will be empty.
Returns:
The OIDCUser instance
"""
# compute an integer user identifier for Django User model
# by concatenating all groups of the UUID4 user identifier
# generated by Keycloak and converting it from hex to decimal
user_id = int("".join(decoded_token["sub"].split("-")), 16)
# create a Django user that will not be saved to database
user = OIDCUser(
id=user_id,
username=decoded_token["preferred_username"],
password="",
first_name=decoded_token["given_name"],
last_name=decoded_token["family_name"],
email=decoded_token["email"],
)
# set is_staff user property based on groups
if "groups" in decoded_token:
user.is_staff = "/staff" in decoded_token["groups"]
if client_id:
# extract user permissions if any
resource_access = decoded_token.get("resource_access", {})
client_resource_access = resource_access.get(client_id, {})
permissions = client_resource_access.get("roles", [])
else:
permissions = []
user.permissions = set(permissions)
# add user sub to custom User proxy model
user.sub = decoded_token["sub"]
return user
def oidc_user_from_profile(
oidc_client: KeycloakOpenIDConnect, oidc_profile: Dict[str, Any]
) -> OIDCUser:
"""Initialize an OIDCUser out of an oidc profile dict.
Args:
oidc_client: KeycloakOpenIDConnect used to discuss with keycloak
oidc_profile: OIDC profile retrieved once connected to keycloak
Returns:
OIDCUser instance parsed out of the token received.
"""
# decode JWT token
decoded_token = oidc_client.decode_token(oidc_profile["access_token"])
# create OIDCUser from decoded token
user = oidc_user_from_decoded_token(decoded_token, client_id=oidc_client.client_id)
# get authentication init datetime
auth_datetime = datetime.fromtimestamp(decoded_token["auth_time"])
exp_datetime = datetime.fromtimestamp(decoded_token["exp"])
# compute OIDC tokens expiration date
oidc_profile["expires_at"] = exp_datetime
oidc_profile["refresh_expires_at"] = auth_datetime + timedelta(
seconds=oidc_profile["refresh_expires_in"]
)
# add OIDC profile data to custom User proxy model
for key, val in oidc_profile.items():
if hasattr(user, key):
setattr(user, key, val)
return user
# 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 django.apps import AppConfig
class TestApp(AppConfig):
name = "swh.auth.tests.app"
# 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 django.db import models
from swh.auth.django.models import OIDCUser
class AppUser(OIDCUser):
"""AppUser class to demonstrate the use of the OIDCUser which adds some attributes.
"""
url = models.TextField(null=False)
class meta:
app_label = "app-label"
SECRET_KEY = "o+&ayiuk(y^wh4ijz5e=c2$$kjj7g^6r%z+8d*c0lbpfs##k#7"
INSTALLED_APPS = [
"django.contrib.auth",
"django.contrib.contenttypes",
"swh.auth.tests.app.apptest.apps.TestApp",
]
# 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
urlpatterns = [] # type: ignore
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault(
"DJANGO_SETTINGS_MODULE", "swh.auth.tests.app.apptest.settings"
)
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
# Copyright (C) 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 swh.auth.pytest_plugin import keycloak_mock_factory
from swh.auth.tests.sample_data import CLIENT_ID, REALM_NAME, SERVER_URL
# keycloak fixture used within tests (cf. test_keycloak.py, test_utils.py)
keycloak_mock = keycloak_mock_factory(
server_url=SERVER_URL, realm_name=REALM_NAME, client_id=CLIENT_ID,
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment