Page MenuHomeSoftware Heritage

D5219.id18694.diff
No OneTemporary

D5219.id18694.diff

diff --git a/mypy.ini b/mypy.ini
--- a/mypy.ini
+++ b/mypy.ini
@@ -13,3 +13,6 @@
[mypy-keycloak.*]
ignore_missing_imports = True
+
+[mypy-django.*]
+ignore_missing_imports = True
diff --git a/requirements-django.txt b/requirements-django.txt
new file mode 100644
--- /dev/null
+++ b/requirements-django.txt
@@ -0,0 +1 @@
+Django<3
diff --git a/requirements-test.txt b/requirements-test.txt
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,2 +1,3 @@
pytest
requests_mock
+pytest-django
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -50,7 +50,10 @@
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]
diff --git a/swh/auth/django.py b/swh/auth/django.py
new file mode 100644
--- /dev/null
+++ b/swh/auth/django.py
@@ -0,0 +1,84 @@
+# 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)
diff --git a/swh/auth/tests/apptest/__init__.py b/swh/auth/tests/apptest/__init__.py
new file mode 100644
--- /dev/null
+++ b/swh/auth/tests/apptest/__init__.py
@@ -0,0 +1 @@
+default_app_config = "swh.auth.tests.apptest.apps.TestApp"
diff --git a/swh/auth/tests/apptest/apps.py b/swh/auth/tests/apptest/apps.py
new file mode 100644
--- /dev/null
+++ b/swh/auth/tests/apptest/apps.py
@@ -0,0 +1,10 @@
+# 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.apptest"
diff --git a/swh/auth/tests/apptest/models.py b/swh/auth/tests/apptest/models.py
new file mode 100644
--- /dev/null
+++ b/swh/auth/tests/apptest/models.py
@@ -0,0 +1,19 @@
+# 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 import OIDCUser
+
+
+class MyUser(OIDCUser):
+ """MyUser class to demonstrate the use of the OIDCUser which adds some attributes to
+ serialize in db."""
+
+ url = models.TextField(null=False)
+
+ class meta:
+ db_table = "user_client"
+ app_label = "app-label"
diff --git a/swh/auth/tests/apptest/urls.py b/swh/auth/tests/apptest/urls.py
new file mode 100644
--- /dev/null
+++ b/swh/auth/tests/apptest/urls.py
@@ -0,0 +1,17 @@
+"""apptest URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/2.2/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+
+urlpatterns = [] # type: ignore
diff --git a/swh/auth/tests/conftest.py b/swh/auth/tests/conftest.py
new file mode 100644
--- /dev/null
+++ b/swh/auth/tests/conftest.py
@@ -0,0 +1,18 @@
+# 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 django.conf import settings
+
+
+def pytest_configure():
+ # Basic settings to avoid having to define a django module settings all over the
+ # place so django tests pass fine
+ settings.configure(
+ INSTALLED_APPS=[
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "swh.auth.tests.apptest",
+ ],
+ )
diff --git a/swh/auth/tests/test_models.py b/swh/auth/tests/test_models.py
new file mode 100644
diff --git a/tox.ini b/tox.ini
--- a/tox.ini
+++ b/tox.ini
@@ -7,6 +7,7 @@
deps =
pytest-cov
dev: pdbpp
+ -r requirements-django.txt
commands =
pytest --doctest-modules \
{envsitepackagesdir}/swh/auth \

File Metadata

Mime Type
text/plain
Expires
Dec 21 2024, 6:31 PM (11 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3221160

Event Timeline