dj-jwt-auth 1.4.0__py3-none-any.whl → 1.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dj_jwt_auth-1.4.0.dist-info → dj_jwt_auth-1.5.0.dist-info}/METADATA +14 -6
- {dj_jwt_auth-1.4.0.dist-info → dj_jwt_auth-1.5.0.dist-info}/RECORD +10 -10
- django_jwt/config.py +9 -2
- django_jwt/settings.py +9 -2
- django_jwt/urls.py +1 -0
- django_jwt/user.py +13 -5
- django_jwt/views.py +27 -7
- tests/test.py +37 -2
- {dj_jwt_auth-1.4.0.dist-info → dj_jwt_auth-1.5.0.dist-info}/WHEEL +0 -0
- {dj_jwt_auth-1.4.0.dist-info → dj_jwt_auth-1.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dj-jwt-auth
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: A Django package for JSON Web Token validation and verification. Using PyJWT.
|
|
5
5
|
Home-page: https://www.example.com/
|
|
6
6
|
Author: Konstantin Seleznev
|
|
@@ -70,14 +70,22 @@ User retated variables:
|
|
|
70
70
|
- OIDC_TOKEN_MODIFIED_FIELD - access token field to store last modified date, by default `updated_at`
|
|
71
71
|
- OIDC_USER_UID - User model" unique identifier, by default `kc_id`
|
|
72
72
|
- OIDC_TOKEN_USER_UID - access token field to store user UID, by default `sub`
|
|
73
|
-
- OIDC_USER_MAPPING - mapping between JWT claims and user model fields
|
|
73
|
+
- OIDC_USER_MAPPING - mapping between JWT claims and user model fields. Can be dict or function. By default:
|
|
74
74
|
```
|
|
75
75
|
OIDC_USER_MAPPING = {
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
76
|
+
"given_name": "first_name",
|
|
77
|
+
"family_name": "last_name",
|
|
78
|
+
"name": "username",
|
|
79
79
|
}
|
|
80
80
|
```
|
|
81
|
+
OR
|
|
82
|
+
```
|
|
83
|
+
def OIDC_USER_MAPPING(userinfo):
|
|
84
|
+
return {
|
|
85
|
+
"first_name": userinfo.get("given_name"),
|
|
86
|
+
"last_name": userinfo.get("family_name"),
|
|
87
|
+
"username": userinfo.get("name"),
|
|
88
|
+
}
|
|
81
89
|
- OIDC_USER_DEFAULTS - default values for user model fields, by default:
|
|
82
90
|
```
|
|
83
91
|
OIDC_USER_DEFAULTS = {
|
|
@@ -94,7 +102,7 @@ These functions should accept two arguments: user and request.
|
|
|
94
102
|
|
|
95
103
|
### Admin panel integration:
|
|
96
104
|
To integrate admin panel with OIDC, add OIDC_ADMIN_ISSUER and OIDC_ADMIN_CLIENT_ID to settings.
|
|
97
|
-
- OIDC_ADMIN_ISSUER -
|
|
105
|
+
- OIDC_ADMIN_ISSUER - for admin-panel access through OIDC. By default will be used 'ES256' from OIDC_CONFIG_ROUTES. Example:
|
|
98
106
|
```
|
|
99
107
|
OIDC_ADMIN_ISSUER = "https://keyCloak/realms/h/.well-known/openid-configuration"
|
|
100
108
|
```
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
django_jwt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
django_jwt/config.py,sha256
|
|
2
|
+
django_jwt/config.py,sha256=-9JkGjMXRVNmQYPvrEwaoJacu068nySNfjMyo5DtXcw,1550
|
|
3
3
|
django_jwt/exceptions.py,sha256=vFJcGOCSZvxJRbSeMgWgPUM9wcXu6CSHblpwMzhV-Ic,198
|
|
4
4
|
django_jwt/middleware.py,sha256=4PiF0-v13aLjvTyeyumQqYFimb6gCDHBgm6KooPGZdM,1176
|
|
5
5
|
django_jwt/pkce.py,sha256=HYIQI0vKSmQkYIqTj3cciIT01ldkjhqlYiXkYcnNSGc,711
|
|
6
6
|
django_jwt/roles.py,sha256=SaHK3o8T8USS4ZhG4SrHPlZQV2lMb2t1UZHT6IQtBvA,143
|
|
7
|
-
django_jwt/settings.py,sha256=
|
|
8
|
-
django_jwt/urls.py,sha256=
|
|
9
|
-
django_jwt/user.py,sha256=
|
|
7
|
+
django_jwt/settings.py,sha256=gJePa3ER0vY6k5sDk-L1VagjbF4_dYrP0zrRJkGNY6Y,1708
|
|
8
|
+
django_jwt/urls.py,sha256=PmNoMxcVg_1oCDHHQJFAcPxhPAOkiMhd4PFnS-Q3JLA,326
|
|
9
|
+
django_jwt/user.py,sha256=WmrSDIz6c5U9sfX0t-XJN3w1bOmsZH-xSTmK1X9BNRA,5241
|
|
10
10
|
django_jwt/utils.py,sha256=Gz8cH0cD3y_cvW8FwRoCFgShBrYvcB7XBF0GWx0n2qQ,1485
|
|
11
|
-
django_jwt/views.py,sha256=
|
|
11
|
+
django_jwt/views.py,sha256=LweS9G_NBeiuVDLhtm_GtOi_Ok6Sz5KJVTU62k91Jcg,4352
|
|
12
12
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
tests/models.py,sha256=K5e0QCgyZeLLHS6i3KRMQHooql47g7qqni7f9tKQrIY,251
|
|
14
|
-
tests/test.py,sha256=
|
|
14
|
+
tests/test.py,sha256=g1Itea87V6hqnK3FGX_nSq0znLRFxPW6WDNTuxYPf3M,8785
|
|
15
15
|
tests/urls.py,sha256=D5FhDSVAudurkrpkCZZPnDvgXSgifwFVB3nAlYBg7uQ,212
|
|
16
|
-
dj_jwt_auth-1.
|
|
17
|
-
dj_jwt_auth-1.
|
|
18
|
-
dj_jwt_auth-1.
|
|
19
|
-
dj_jwt_auth-1.
|
|
16
|
+
dj_jwt_auth-1.5.0.dist-info/METADATA,sha256=AktxVxd6quOwSseEepUpp7BiiBspNSmEnLwTUVS7nHg,4369
|
|
17
|
+
dj_jwt_auth-1.5.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
18
|
+
dj_jwt_auth-1.5.0.dist-info/top_level.txt,sha256=58O7TdK-yECZcbmPc52KNlBFpjIUlENuZubCxaSOxus,17
|
|
19
|
+
dj_jwt_auth-1.5.0.dist-info/RECORD,,
|
django_jwt/config.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from functools import cache
|
|
3
|
+
from urllib.parse import urljoin
|
|
3
4
|
|
|
4
5
|
import requests
|
|
5
6
|
from jwt.algorithms import ECAlgorithm, RSAAlgorithm
|
|
@@ -8,6 +9,12 @@ from django_jwt import settings
|
|
|
8
9
|
from django_jwt.exceptions import ConfigException
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
def ensure_well_known(url: str) -> str:
|
|
13
|
+
if url.endswith(".well-known/openid-configuration"):
|
|
14
|
+
return url
|
|
15
|
+
return urljoin(url, ".well-known/openid-configuration")
|
|
16
|
+
|
|
17
|
+
|
|
11
18
|
class Config:
|
|
12
19
|
def __init__(self):
|
|
13
20
|
self.route = settings.OIDC_CONFIG_ROUTES
|
|
@@ -17,7 +24,7 @@ class Config:
|
|
|
17
24
|
if not self.route:
|
|
18
25
|
raise ConfigException("OIDC_CONFIG_ROUTES is not set")
|
|
19
26
|
|
|
20
|
-
response = requests.get(self.route[alg])
|
|
27
|
+
response = requests.get(ensure_well_known(self.route[alg]))
|
|
21
28
|
response.raise_for_status()
|
|
22
29
|
return response.json()
|
|
23
30
|
|
|
@@ -35,7 +42,7 @@ class Config:
|
|
|
35
42
|
@cache
|
|
36
43
|
def admin(self) -> dict:
|
|
37
44
|
if settings.OIDC_ADMIN_ISSUER:
|
|
38
|
-
response = requests.get(settings.OIDC_ADMIN_ISSUER)
|
|
45
|
+
response = requests.get(ensure_well_known(settings.OIDC_ADMIN_ISSUER))
|
|
39
46
|
response.raise_for_status()
|
|
40
47
|
return response.json()
|
|
41
48
|
raise ConfigException("OIDC_ADMIN_ISSUER is not set")
|
django_jwt/settings.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
|
+
|
|
2
3
|
from django_jwt.roles import ROLE
|
|
3
4
|
|
|
4
5
|
OIDC_AUDIENCE = getattr(settings, "OIDC_AUDIENCE", ["account", "broker"])
|
|
5
|
-
OIDC_CONFIG_URL = getattr(settings, "OIDC_CONFIG_URL", None)
|
|
6
6
|
|
|
7
7
|
# key from KeyCloak; value is user model
|
|
8
8
|
OIDC_USER_UPDATE = getattr(settings, "OIDC_USER_UPDATE", True)
|
|
@@ -37,11 +37,18 @@ OIDC_USER_ON_UPDATE = getattr(
|
|
|
37
37
|
None,
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
-
OIDC_CONFIG_ROUTES = getattr(settings, "OIDC_CONFIG_ROUTES",
|
|
40
|
+
OIDC_CONFIG_ROUTES = getattr(settings, "OIDC_CONFIG_ROUTES", {})
|
|
41
41
|
OIDC_ADMIN_ISSUER = getattr(settings, "OIDC_ADMIN_ISSUER", None)
|
|
42
42
|
OIDC_ADMIN_CLIENT_ID = getattr(settings, "OIDC_ADMIN_CLIENT_ID", "cs-completeanatomy-admin")
|
|
43
43
|
OIDC_ADMIN_SCOPE = getattr(settings, "OIDC_ADMIN_SCOPE", "openid")
|
|
44
44
|
OIDC_ADMIN_ROLES = getattr(settings, "OIDC_ADMIN_ROLES", [])
|
|
45
45
|
|
|
46
|
+
if not OIDC_ADMIN_ISSUER:
|
|
47
|
+
OIDC_ADMIN_ISSUER = OIDC_CONFIG_ROUTES.get("ES256", None)
|
|
48
|
+
|
|
46
49
|
for role in OIDC_ADMIN_ROLES:
|
|
47
50
|
assert isinstance(role, ROLE), f"Role must be a namedtuple, got {type(role)}"
|
|
51
|
+
|
|
52
|
+
assert isinstance(OIDC_USER_MAPPING, dict) or callable(
|
|
53
|
+
OIDC_USER_MAPPING
|
|
54
|
+
), "OIDC_USER_MAPPING must be a dict or function"
|
django_jwt/urls.py
CHANGED
|
@@ -4,5 +4,6 @@ from django_jwt import views
|
|
|
4
4
|
|
|
5
5
|
urlpatterns = [
|
|
6
6
|
path("oidc/callback/", views.ReceiveRedirectView.as_view(), name="receive_redirect_view"),
|
|
7
|
+
path("oidc/logout/", views.silent_sso_check, name="silent_sso_check"),
|
|
7
8
|
path("oidc/", views.StartOIDCAuthView.as_view(), name="start_oidc_auth"),
|
|
8
9
|
]
|
django_jwt/user.py
CHANGED
|
@@ -16,6 +16,12 @@ log = getLogger(__name__)
|
|
|
16
16
|
model = get_user_model()
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def mapper(user_data: dict) -> dict:
|
|
20
|
+
if callable(settings.OIDC_USER_MAPPING):
|
|
21
|
+
return settings.OIDC_USER_MAPPING(user_data)
|
|
22
|
+
return {ca_key: user_data[kc_key] for kc_key, ca_key in settings.OIDC_USER_MAPPING.items() if kc_key in user_data}
|
|
23
|
+
|
|
24
|
+
|
|
19
25
|
class UserHandler:
|
|
20
26
|
modified_at = None
|
|
21
27
|
|
|
@@ -38,9 +44,8 @@ class UserHandler:
|
|
|
38
44
|
|
|
39
45
|
user_data = oidc_handler.get_user_info(self.access_token)
|
|
40
46
|
self.kwargs["email"] = user_data["email"].lower()
|
|
41
|
-
self.kwargs.update(
|
|
42
|
-
|
|
43
|
-
)
|
|
47
|
+
self.kwargs.update(mapper(user_data))
|
|
48
|
+
log.info(f"User data: {self.kwargs}, access_token: {self.access_token}")
|
|
44
49
|
|
|
45
50
|
def _update_user(self, user):
|
|
46
51
|
"""Update user fields if they are changed"""
|
|
@@ -129,17 +134,20 @@ class RoleHandler:
|
|
|
129
134
|
def get_groups(self, role_name: str) -> Group:
|
|
130
135
|
return Group.objects.filter(name__in=self.roles[role_name].groups)
|
|
131
136
|
|
|
132
|
-
def apply(self, user: model, access_token: dict):
|
|
137
|
+
def apply(self, user: model, access_token: dict) -> list[str]:
|
|
133
138
|
token_roles = access_token.get("resource_access", {}).get(settings.OIDC_ADMIN_CLIENT_ID, {}).get("roles", [])
|
|
134
139
|
for role_name in token_roles:
|
|
135
140
|
if role_name in self.roles:
|
|
136
141
|
role = self.roles[role_name]
|
|
137
142
|
user.groups.add(*self.get_groups(role_name))
|
|
138
143
|
user.user_permissions.add(*self.get_permissions(role_name))
|
|
144
|
+
user.is_staff = True
|
|
139
145
|
if role.is_superuser != user.is_superuser:
|
|
140
146
|
user.is_superuser = role.is_superuser
|
|
141
|
-
|
|
147
|
+
user.save(update_fields=["is_superuser", "is_staff"])
|
|
142
148
|
break
|
|
143
149
|
|
|
150
|
+
return token_roles
|
|
151
|
+
|
|
144
152
|
|
|
145
153
|
role_handler = RoleHandler()
|
django_jwt/views.py
CHANGED
|
@@ -8,7 +8,7 @@ from django.conf import settings
|
|
|
8
8
|
from django.contrib.auth import login
|
|
9
9
|
from django.core.cache import cache
|
|
10
10
|
from django.http.response import HttpResponse
|
|
11
|
-
from django.shortcuts import redirect
|
|
11
|
+
from django.shortcuts import redirect, render
|
|
12
12
|
from django.urls import reverse
|
|
13
13
|
from django.views import View
|
|
14
14
|
from requests.exceptions import HTTPError
|
|
@@ -27,19 +27,34 @@ def silent_sso_check(request):
|
|
|
27
27
|
return HttpResponse("<html><body><script>parent.postMessage(location.href, location.origin)</script></body></html>")
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def index_response(request, msg, status=400):
|
|
31
|
+
logout_url = config.admin().get("end_session_endpoint")
|
|
32
|
+
return render(
|
|
33
|
+
request,
|
|
34
|
+
"django-jwt-index.html",
|
|
35
|
+
{
|
|
36
|
+
"error_message": msg,
|
|
37
|
+
"login_url": reverse("start_oidc_auth"),
|
|
38
|
+
"logout_url": logout_url,
|
|
39
|
+
"redirect_uri": request.build_absolute_uri(reverse("start_oidc_auth")),
|
|
40
|
+
},
|
|
41
|
+
status=status,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
30
45
|
class AbsView(View):
|
|
31
46
|
def dispatch(self, request, *args, **kwargs):
|
|
32
47
|
try:
|
|
33
48
|
return super().dispatch(request, *args, **kwargs)
|
|
34
49
|
except HTTPError as exc:
|
|
35
50
|
log.warning(f"OIDC Admin HTTPError: {exc}")
|
|
36
|
-
return
|
|
51
|
+
return index_response(request=request, msg=exc.response.text, status=exc.response.status_code)
|
|
37
52
|
except ConfigException as exc:
|
|
38
53
|
return HttpResponse(content=str(exc), status=500)
|
|
39
54
|
except BadRequestException as exc:
|
|
40
|
-
return
|
|
41
|
-
except Exception:
|
|
42
|
-
return
|
|
55
|
+
return index_response(request=request, msg=str(exc))
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
return index_response(request=request, msg=str(exc))
|
|
43
58
|
|
|
44
59
|
|
|
45
60
|
class StartOIDCAuthView(AbsView):
|
|
@@ -81,10 +96,15 @@ class ReceiveRedirectView(AbsView):
|
|
|
81
96
|
data = oidc_handler.decode_token(token)
|
|
82
97
|
user = UserHandler(data, request, token).get_user()
|
|
83
98
|
log.info(f"OIDC Admin login: {user}", extra={"data": data})
|
|
84
|
-
role_handler.apply(user, data)
|
|
99
|
+
roles = role_handler.apply(user, data)
|
|
85
100
|
if not user.is_staff:
|
|
86
|
-
raise BadRequestException("User is not staff")
|
|
101
|
+
raise BadRequestException(f"User {user.email} is not staff\nRoles: {roles}")
|
|
87
102
|
login(request, user, backend=settings.DEFAULT_AUTHENTICATION_BACKEND)
|
|
88
103
|
return redirect("admin:index")
|
|
89
104
|
|
|
90
105
|
raise BadRequestException("No PKCE secret found in cache")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class LogoutView(AbsView):
|
|
109
|
+
def get(self, request):
|
|
110
|
+
return index_response(request, "Logged out", status=401)
|
tests/test.py
CHANGED
|
@@ -3,14 +3,14 @@ from unittest.mock import Mock, patch
|
|
|
3
3
|
|
|
4
4
|
from django.contrib.auth import get_user_model
|
|
5
5
|
from django.contrib.auth.models import Group, Permission
|
|
6
|
-
from django.test import TestCase
|
|
6
|
+
from django.test import TestCase, override_settings
|
|
7
7
|
from django.urls import reverse
|
|
8
8
|
from jwt.api_jwt import ExpiredSignatureError
|
|
9
9
|
|
|
10
10
|
from django_jwt import settings
|
|
11
11
|
from django_jwt.middleware import JWTAuthMiddleware
|
|
12
|
-
from django_jwt.user import role_handler
|
|
13
12
|
from django_jwt.roles import ROLE
|
|
13
|
+
from django_jwt.user import role_handler
|
|
14
14
|
|
|
15
15
|
access_token_payload = {
|
|
16
16
|
"sub": "1234",
|
|
@@ -36,6 +36,16 @@ def _on_update(user, request):
|
|
|
36
36
|
user.save()
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def test_mapper(user_data: dict) -> dict:
|
|
40
|
+
"""Override user data - set username to 'override'"""
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
"username": "override",
|
|
44
|
+
"first_name": user_data["given_name"],
|
|
45
|
+
"last_name": user_data["family_name"],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
39
49
|
@patch("django_jwt.utils.OIDCHandler.decode_token", return_value=access_token_payload)
|
|
40
50
|
@patch("django_jwt.utils.OIDCHandler.get_user_info", return_value=user_info_payload)
|
|
41
51
|
class OIDCHandlerTest(TestCase):
|
|
@@ -43,6 +53,11 @@ class OIDCHandlerTest(TestCase):
|
|
|
43
53
|
self.middleware = JWTAuthMiddleware(get_response=lambda x: x)
|
|
44
54
|
self.request = Mock()
|
|
45
55
|
self.request.META = {"HTTP_AUTHORIZATION": "Bearer 1234"}
|
|
56
|
+
settings.OIDC_USER_MAPPING = { # default mapping
|
|
57
|
+
"given_name": "first_name",
|
|
58
|
+
"family_name": "last_name",
|
|
59
|
+
"name": "username",
|
|
60
|
+
}
|
|
46
61
|
|
|
47
62
|
def assertUserWithPayload(self):
|
|
48
63
|
self.assertEqual(self.request.user.first_name, user_info_payload["given_name"])
|
|
@@ -135,6 +150,22 @@ class OIDCHandlerTest(TestCase):
|
|
|
135
150
|
self.middleware.process_request(self.request)
|
|
136
151
|
self.assertEqual(self.request.user.username, "on_update")
|
|
137
152
|
|
|
153
|
+
def test_user_data_mapping(self, *_):
|
|
154
|
+
"""User data is mapped"""
|
|
155
|
+
|
|
156
|
+
settings.OIDC_USER_MAPPING = {"name": "username", "given_name": "last_name", "family_name": "first_name"}
|
|
157
|
+
self.middleware.process_request(self.request)
|
|
158
|
+
self.assertEqual(self.request.user.username, user_info_payload["name"])
|
|
159
|
+
self.assertEqual(self.request.user.first_name, user_info_payload["family_name"])
|
|
160
|
+
self.assertEqual(self.request.user.last_name, user_info_payload["given_name"])
|
|
161
|
+
|
|
162
|
+
def test_user_data_mapping_callable(self, *_):
|
|
163
|
+
"""User data is mapped"""
|
|
164
|
+
|
|
165
|
+
settings.OIDC_USER_MAPPING = test_mapper
|
|
166
|
+
self.middleware.process_request(self.request)
|
|
167
|
+
self.assertEqual(self.request.user.username, "override")
|
|
168
|
+
|
|
138
169
|
|
|
139
170
|
class RolesTest(TestCase):
|
|
140
171
|
def setUp(self) -> None:
|
|
@@ -160,21 +191,25 @@ class RolesTest(TestCase):
|
|
|
160
191
|
self.assertTrue(self.user.groups.filter(name="staff group").exists())
|
|
161
192
|
self.assertTrue(self.user.user_permissions.filter(codename="add_user").exists())
|
|
162
193
|
self.assertFalse(self.user.is_superuser)
|
|
194
|
+
self.assertTrue(self.user.is_staff)
|
|
163
195
|
|
|
164
196
|
def test_admin_role(self):
|
|
165
197
|
self.access_token["resource_access"][settings.OIDC_ADMIN_CLIENT_ID]["roles"] = ["admin"]
|
|
166
198
|
role_handler.apply(self.user, self.access_token)
|
|
167
199
|
self.assertTrue(self.user.is_superuser)
|
|
200
|
+
self.assertTrue(self.user.is_staff)
|
|
168
201
|
|
|
169
202
|
def test_apply_staff_then_admin_role(self):
|
|
170
203
|
self.access_token["resource_access"][settings.OIDC_ADMIN_CLIENT_ID]["roles"] = ["staff"]
|
|
171
204
|
role_handler.apply(self.user, self.access_token)
|
|
172
205
|
self.assertFalse(self.user.is_superuser)
|
|
206
|
+
self.assertTrue(self.user.is_staff)
|
|
173
207
|
self.assertTrue(self.user.groups.filter(name="staff group").exists())
|
|
174
208
|
self.assertTrue(self.user.user_permissions.filter(codename="add_user").exists())
|
|
175
209
|
|
|
176
210
|
self.access_token["resource_access"][settings.OIDC_ADMIN_CLIENT_ID]["roles"] = ["admin"]
|
|
177
211
|
role_handler.apply(self.user, self.access_token)
|
|
178
212
|
self.assertTrue(self.user.is_superuser)
|
|
213
|
+
self.assertTrue(self.user.is_staff)
|
|
179
214
|
self.assertTrue(self.user.groups.filter(name="staff group").exists())
|
|
180
215
|
self.assertTrue(self.user.user_permissions.filter(codename="add_user").exists())
|
|
File without changes
|
|
File without changes
|