dj-jwt-auth 1.2.0__tar.gz → 1.3.0__tar.gz

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.
Files changed (26) hide show
  1. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/PKG-INFO +51 -21
  2. dj-jwt-auth-1.3.0/README.md +99 -0
  3. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/dj_jwt_auth.egg-info/PKG-INFO +51 -21
  4. dj-jwt-auth-1.3.0/django_jwt/exceptions.py +10 -0
  5. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/django_jwt/settings.py +6 -0
  6. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/django_jwt/user.py +46 -0
  7. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/django_jwt/views.py +14 -8
  8. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/setup.cfg +1 -1
  9. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/tests/test.py +46 -7
  10. dj-jwt-auth-1.2.0/README.md +0 -65
  11. dj-jwt-auth-1.2.0/django_jwt/exceptions.py +0 -4
  12. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/dj_jwt_auth.egg-info/SOURCES.txt +0 -0
  13. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/dj_jwt_auth.egg-info/dependency_links.txt +0 -0
  14. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/dj_jwt_auth.egg-info/requires.txt +0 -0
  15. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/dj_jwt_auth.egg-info/top_level.txt +0 -0
  16. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/django_jwt/__init__.py +0 -0
  17. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/django_jwt/config.py +0 -0
  18. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/django_jwt/middleware.py +0 -0
  19. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/django_jwt/pkce.py +0 -0
  20. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/django_jwt/urls.py +0 -0
  21. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/django_jwt/utils.py +0 -0
  22. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/pyproject.toml +0 -0
  23. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/setup.py +0 -0
  24. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/tests/__init__.py +0 -0
  25. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/tests/models.py +0 -0
  26. {dj-jwt-auth-1.2.0 → dj-jwt-auth-1.3.0}/tests/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dj-jwt-auth
3
- Version: 1.2.0
3
+ Version: 1.3.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
@@ -21,23 +21,22 @@ Classifier: Topic :: Internet :: WWW/HTTP
21
21
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
22
22
  Requires-Python: >=3.8
23
23
  Description-Content-Type: text/markdown
24
- Requires-Dist: Django>=3.0
25
- Requires-Dist: pyjwt>=2.5.0
26
- Requires-Dist: requests>=2.28.1
27
- Requires-Dist: cryptography>=36.0.2
28
24
 
29
25
  # Django-JWT
30
26
 
31
27
  This is a package to verify and validate JSON Web Tokens (JWT) in Django.
32
28
 
33
29
  ### Installation
34
- 1. Install the package using pip.
30
+ 1. Install the package using pip:
31
+ ```bash
32
+ pip install dj-jwt-auth
33
+ ```
35
34
 
36
35
  2. Add "django_jwt" to your INSTALLED_APPS setting like this::
37
36
  ```
38
37
  INSTALLED_APPS = [
39
38
  ...
40
- 'django_jwt',
39
+ "django_jwt",
41
40
  ]
42
41
  ```
43
42
 
@@ -45,33 +44,39 @@ This is a package to verify and validate JSON Web Tokens (JWT) in Django.
45
44
  ```
46
45
  MIDDLEWARE = [
47
46
  ...
48
- 'django_jwt.middleware.JWTAuthMiddleware',
47
+ "django_jwt.middleware.JWTAuthMiddleware",
49
48
  ]
50
49
  ```
51
50
 
52
51
  ### Configuration:
53
52
  Required variables:
54
- - OIDC_CERTS_URL - certificate endpoint, like `https://keyCloak/realms/h/.well-known/openid-configuration`
55
-
53
+ - OIDC_CONFIG_ROUTES - dict of "algorithm": "config_url". Required for using JWTAuthMiddleware. Example:
54
+ ```
55
+ OIDC_CONFIG_ROUTES = {
56
+ "RS256": "https://keyCloak/realms/h/.well-known/openid-configuration",
57
+ "HS256": "https://keyCloak/realms/h/.well-known/openid-configuration",
58
+ }
59
+ ```
56
60
  Optional variables:
57
61
  - OIDC_AUDIENCE - by default ["account", "broker"]
62
+
58
63
  User retated variables:
59
64
  - OIDC_USER_UPDATE - if True, user model will be updated from userinfo endpoint if MODIFIED date has changed, by default True
60
65
  - OIDC_USER_MODIFIED_FIELD - user model field to store last modified date, by default `modified_timestamp`
61
66
  - OIDC_TOKEN_MODIFIED_FIELD - access token field to store last modified date, by default `updated_at`
62
- - OIDC_USER_UID - User model' unique identifier, by default `kc_id`
67
+ - OIDC_USER_UID - User model" unique identifier, by default `kc_id`
63
68
  - OIDC_USER_MAPPING - mapping between JWT claims and user model fields, by default:
64
69
  ```
65
70
  OIDC_USER_MAPPING = {
66
- 'first_name': 'first_name',
67
- 'last_name': 'last_name',
68
- 'username': 'username',
71
+ "first_name": "first_name",
72
+ "last_name": "last_name",
73
+ "username": "username",
69
74
  }
70
75
  ```
71
76
  - OIDC_USER_DEFAULTS - default values for user model fields, by default:
72
77
  ```
73
78
  OIDC_USER_DEFAULTS = {
74
- 'is_active': True,
79
+ "is_active": True,
75
80
  }
76
81
  ```
77
82
 
@@ -80,14 +85,39 @@ User retated variables:
80
85
  OIDC_USER_ON_CREATE = None
81
86
  OIDC_USER_ON_UPDATE = None
82
87
  ```
83
- - OIDC_CONFIG_ROUTES - dict of 'algorithm': 'config_url', by default is empty. If filled will be used instead of OIDC_CERTS_URL
88
+ These functions should accept two arguments: user and request.
89
+
90
+ ### Admin panel integration:
91
+ To integrate admin panel with OIDC, add OIDC_ADMIN_ISSUER and OIDC_ADMIN_CLIENT_ID to settings.
92
+ - OIDC_ADMIN_ISSUER - required for admin-panel access through OIDC. Example:
84
93
  ```
85
- OIDC_CONFIG_ROUTES = {
86
- 'RS256': 'https://keyCloak/realms/h/.well-known/openid-configuration',
87
- 'HS256': 'https://keyCloak/realms/h/.well-known/openid-configuration',
88
- }
94
+ OIDC_ADMIN_ISSUER = "https://keyCloak/realms/h/.well-known/openid-configuration"
89
95
  ```
90
- These functions should accept two arguments: user and request.
96
+ - OIDC_ADMIN_CLIENT_ID - by default "complete-anatomy"
97
+ To mapping roles to admin panel permissions, use OIDC_ADMIN_ROLES. Example:
98
+ ```python
99
+ from django_jwt.user import ROLE
100
+
101
+ OIDC_ADMIN_ROLES = [
102
+ ROLE(
103
+ name="admin", # name from token
104
+ is_superuser=True,
105
+ ),
106
+ ROLE(
107
+ name="staff",
108
+ groups=["LMS (Full)", "Organizations (Full)", "Customer Support (Full)"],
109
+ permissions=["Can add user"],
110
+ ),
111
+ ]
112
+ ```
113
+ And add login view to urls.py:
114
+ ```python
115
+ urlpatterns = [
116
+ path("admin/", include("django_jwt.urls")),
117
+ ...
118
+ ]
119
+ ```
120
+ Login URL will be available at `/admin/oidc/`.
91
121
 
92
122
  ### Testing:
93
123
  Run command `python runtests.py` to run tests.
@@ -0,0 +1,99 @@
1
+ # Django-JWT
2
+
3
+ This is a package to verify and validate JSON Web Tokens (JWT) in Django.
4
+
5
+ ### Installation
6
+ 1. Install the package using pip:
7
+ ```bash
8
+ pip install dj-jwt-auth
9
+ ```
10
+
11
+ 2. Add "django_jwt" to your INSTALLED_APPS setting like this::
12
+ ```
13
+ INSTALLED_APPS = [
14
+ ...
15
+ "django_jwt",
16
+ ]
17
+ ```
18
+
19
+ 3. Add "django_jwt.middleware.JWTAuthMiddleware" to your MIDDLEWARE setting like this::
20
+ ```
21
+ MIDDLEWARE = [
22
+ ...
23
+ "django_jwt.middleware.JWTAuthMiddleware",
24
+ ]
25
+ ```
26
+
27
+ ### Configuration:
28
+ Required variables:
29
+ - OIDC_CONFIG_ROUTES - dict of "algorithm": "config_url". Required for using JWTAuthMiddleware. Example:
30
+ ```
31
+ OIDC_CONFIG_ROUTES = {
32
+ "RS256": "https://keyCloak/realms/h/.well-known/openid-configuration",
33
+ "HS256": "https://keyCloak/realms/h/.well-known/openid-configuration",
34
+ }
35
+ ```
36
+ Optional variables:
37
+ - OIDC_AUDIENCE - by default ["account", "broker"]
38
+
39
+ User retated variables:
40
+ - OIDC_USER_UPDATE - if True, user model will be updated from userinfo endpoint if MODIFIED date has changed, by default True
41
+ - OIDC_USER_MODIFIED_FIELD - user model field to store last modified date, by default `modified_timestamp`
42
+ - OIDC_TOKEN_MODIFIED_FIELD - access token field to store last modified date, by default `updated_at`
43
+ - OIDC_USER_UID - User model" unique identifier, by default `kc_id`
44
+ - OIDC_USER_MAPPING - mapping between JWT claims and user model fields, by default:
45
+ ```
46
+ OIDC_USER_MAPPING = {
47
+ "first_name": "first_name",
48
+ "last_name": "last_name",
49
+ "username": "username",
50
+ }
51
+ ```
52
+ - OIDC_USER_DEFAULTS - default values for user model fields, by default:
53
+ ```
54
+ OIDC_USER_DEFAULTS = {
55
+ "is_active": True,
56
+ }
57
+ ```
58
+
59
+ - OIDC_USER_ON_CREATE and OIDC_USER_ON_UPDATE - functions to be called on user creation and update, by default:
60
+ ```
61
+ OIDC_USER_ON_CREATE = None
62
+ OIDC_USER_ON_UPDATE = None
63
+ ```
64
+ These functions should accept two arguments: user and request.
65
+
66
+ ### Admin panel integration:
67
+ To integrate admin panel with OIDC, add OIDC_ADMIN_ISSUER and OIDC_ADMIN_CLIENT_ID to settings.
68
+ - OIDC_ADMIN_ISSUER - required for admin-panel access through OIDC. Example:
69
+ ```
70
+ OIDC_ADMIN_ISSUER = "https://keyCloak/realms/h/.well-known/openid-configuration"
71
+ ```
72
+ - OIDC_ADMIN_CLIENT_ID - by default "complete-anatomy"
73
+ To mapping roles to admin panel permissions, use OIDC_ADMIN_ROLES. Example:
74
+ ```python
75
+ from django_jwt.user import ROLE
76
+
77
+ OIDC_ADMIN_ROLES = [
78
+ ROLE(
79
+ name="admin", # name from token
80
+ is_superuser=True,
81
+ ),
82
+ ROLE(
83
+ name="staff",
84
+ groups=["LMS (Full)", "Organizations (Full)", "Customer Support (Full)"],
85
+ permissions=["Can add user"],
86
+ ),
87
+ ]
88
+ ```
89
+ And add login view to urls.py:
90
+ ```python
91
+ urlpatterns = [
92
+ path("admin/", include("django_jwt.urls")),
93
+ ...
94
+ ]
95
+ ```
96
+ Login URL will be available at `/admin/oidc/`.
97
+
98
+ ### Testing:
99
+ Run command `python runtests.py` to run tests.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dj-jwt-auth
3
- Version: 1.2.0
3
+ Version: 1.3.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
@@ -21,23 +21,22 @@ Classifier: Topic :: Internet :: WWW/HTTP
21
21
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
22
22
  Requires-Python: >=3.8
23
23
  Description-Content-Type: text/markdown
24
- Requires-Dist: Django>=3.0
25
- Requires-Dist: pyjwt>=2.5.0
26
- Requires-Dist: requests>=2.28.1
27
- Requires-Dist: cryptography>=36.0.2
28
24
 
29
25
  # Django-JWT
30
26
 
31
27
  This is a package to verify and validate JSON Web Tokens (JWT) in Django.
32
28
 
33
29
  ### Installation
34
- 1. Install the package using pip.
30
+ 1. Install the package using pip:
31
+ ```bash
32
+ pip install dj-jwt-auth
33
+ ```
35
34
 
36
35
  2. Add "django_jwt" to your INSTALLED_APPS setting like this::
37
36
  ```
38
37
  INSTALLED_APPS = [
39
38
  ...
40
- 'django_jwt',
39
+ "django_jwt",
41
40
  ]
42
41
  ```
43
42
 
@@ -45,33 +44,39 @@ This is a package to verify and validate JSON Web Tokens (JWT) in Django.
45
44
  ```
46
45
  MIDDLEWARE = [
47
46
  ...
48
- 'django_jwt.middleware.JWTAuthMiddleware',
47
+ "django_jwt.middleware.JWTAuthMiddleware",
49
48
  ]
50
49
  ```
51
50
 
52
51
  ### Configuration:
53
52
  Required variables:
54
- - OIDC_CERTS_URL - certificate endpoint, like `https://keyCloak/realms/h/.well-known/openid-configuration`
55
-
53
+ - OIDC_CONFIG_ROUTES - dict of "algorithm": "config_url". Required for using JWTAuthMiddleware. Example:
54
+ ```
55
+ OIDC_CONFIG_ROUTES = {
56
+ "RS256": "https://keyCloak/realms/h/.well-known/openid-configuration",
57
+ "HS256": "https://keyCloak/realms/h/.well-known/openid-configuration",
58
+ }
59
+ ```
56
60
  Optional variables:
57
61
  - OIDC_AUDIENCE - by default ["account", "broker"]
62
+
58
63
  User retated variables:
59
64
  - OIDC_USER_UPDATE - if True, user model will be updated from userinfo endpoint if MODIFIED date has changed, by default True
60
65
  - OIDC_USER_MODIFIED_FIELD - user model field to store last modified date, by default `modified_timestamp`
61
66
  - OIDC_TOKEN_MODIFIED_FIELD - access token field to store last modified date, by default `updated_at`
62
- - OIDC_USER_UID - User model' unique identifier, by default `kc_id`
67
+ - OIDC_USER_UID - User model" unique identifier, by default `kc_id`
63
68
  - OIDC_USER_MAPPING - mapping between JWT claims and user model fields, by default:
64
69
  ```
65
70
  OIDC_USER_MAPPING = {
66
- 'first_name': 'first_name',
67
- 'last_name': 'last_name',
68
- 'username': 'username',
71
+ "first_name": "first_name",
72
+ "last_name": "last_name",
73
+ "username": "username",
69
74
  }
70
75
  ```
71
76
  - OIDC_USER_DEFAULTS - default values for user model fields, by default:
72
77
  ```
73
78
  OIDC_USER_DEFAULTS = {
74
- 'is_active': True,
79
+ "is_active": True,
75
80
  }
76
81
  ```
77
82
 
@@ -80,14 +85,39 @@ User retated variables:
80
85
  OIDC_USER_ON_CREATE = None
81
86
  OIDC_USER_ON_UPDATE = None
82
87
  ```
83
- - OIDC_CONFIG_ROUTES - dict of 'algorithm': 'config_url', by default is empty. If filled will be used instead of OIDC_CERTS_URL
88
+ These functions should accept two arguments: user and request.
89
+
90
+ ### Admin panel integration:
91
+ To integrate admin panel with OIDC, add OIDC_ADMIN_ISSUER and OIDC_ADMIN_CLIENT_ID to settings.
92
+ - OIDC_ADMIN_ISSUER - required for admin-panel access through OIDC. Example:
84
93
  ```
85
- OIDC_CONFIG_ROUTES = {
86
- 'RS256': 'https://keyCloak/realms/h/.well-known/openid-configuration',
87
- 'HS256': 'https://keyCloak/realms/h/.well-known/openid-configuration',
88
- }
94
+ OIDC_ADMIN_ISSUER = "https://keyCloak/realms/h/.well-known/openid-configuration"
89
95
  ```
90
- These functions should accept two arguments: user and request.
96
+ - OIDC_ADMIN_CLIENT_ID - by default "complete-anatomy"
97
+ To mapping roles to admin panel permissions, use OIDC_ADMIN_ROLES. Example:
98
+ ```python
99
+ from django_jwt.user import ROLE
100
+
101
+ OIDC_ADMIN_ROLES = [
102
+ ROLE(
103
+ name="admin", # name from token
104
+ is_superuser=True,
105
+ ),
106
+ ROLE(
107
+ name="staff",
108
+ groups=["LMS (Full)", "Organizations (Full)", "Customer Support (Full)"],
109
+ permissions=["Can add user"],
110
+ ),
111
+ ]
112
+ ```
113
+ And add login view to urls.py:
114
+ ```python
115
+ urlpatterns = [
116
+ path("admin/", include("django_jwt.urls")),
117
+ ...
118
+ ]
119
+ ```
120
+ Login URL will be available at `/admin/oidc/`.
91
121
 
92
122
  ### Testing:
93
123
  Run command `python runtests.py` to run tests.
@@ -0,0 +1,10 @@
1
+ class ConfigException(Exception):
2
+ """Base class for exceptions in this module."""
3
+
4
+ pass
5
+
6
+
7
+ class BadRequestException(Exception):
8
+ """Base class for exceptions in this module."""
9
+
10
+ pass
@@ -39,3 +39,9 @@ OIDC_CONFIG_ROUTES = getattr(settings, "OIDC_CONFIG_ROUTES", None)
39
39
  OIDC_ADMIN_ISSUER = getattr(settings, "OIDC_ADMIN_ISSUER", None)
40
40
  OIDC_ADMIN_CLIENT_ID = getattr(settings, "OIDC_ADMIN_CLIENT_ID", "complete-anatomy")
41
41
  OIDC_ADMIN_SCOPE = getattr(settings, "OIDC_ADMIN_SCOPE", "openid")
42
+ OIDC_ADMIN_ROLES = getattr(settings, "OIDC_ADMIN_ROLES", [])
43
+
44
+ for role in OIDC_ADMIN_ROLES:
45
+ from django_jwt.user import ROLE
46
+
47
+ assert isinstance(role, ROLE), f"Role must be a namedtuple, got {type(role)}"
@@ -1,8 +1,11 @@
1
+ from collections import namedtuple
1
2
  from datetime import datetime
3
+ from functools import cache
2
4
  from logging import getLogger
3
5
 
4
6
  import pytz
5
7
  from django.contrib.auth import get_user_model
8
+ from django.contrib.auth.models import Group, Permission
6
9
  from django.http.request import HttpRequest
7
10
 
8
11
  from django_jwt import settings
@@ -12,6 +15,7 @@ utc = pytz.UTC
12
15
  log = getLogger(__name__)
13
16
 
14
17
  model = get_user_model()
18
+ ROLE = namedtuple("Role", ["name", "is_superuser", "groups", "permissions"], defaults=["", False, [], []])
15
19
 
16
20
 
17
21
  class UserHandler:
@@ -100,3 +104,45 @@ class UserHandler:
100
104
  return self._get_by_email()
101
105
  except model.DoesNotExist:
102
106
  return self._create_new_user()
107
+
108
+
109
+ class RoleHandler:
110
+ """
111
+ Process user roles and permissions from access token.
112
+ Token be like:
113
+ ...
114
+ "resource_access": {
115
+ "complete_anatomy": {
116
+ "roles": [
117
+ "admin"
118
+ ]
119
+ }
120
+ },
121
+ """
122
+
123
+ @property
124
+ def roles(self) -> dict:
125
+ return {role.name: role for role in settings.OIDC_ADMIN_ROLES}
126
+
127
+ @cache
128
+ def get_permissions(self, role_name: str) -> Permission:
129
+ return Permission.objects.filter(codename__in=self.roles[role_name].permissions)
130
+
131
+ @cache
132
+ def get_groups(self, role_name: str) -> Group:
133
+ return Group.objects.filter(name__in=self.roles[role_name].groups)
134
+
135
+ def apply(self, user: model, access_token: dict):
136
+ token_roles = access_token.get("resource_access", {}).get(settings.OIDC_ADMIN_CLIENT_ID, {}).get("roles", [])
137
+ for role_name in token_roles:
138
+ if role_name in self.roles:
139
+ role = self.roles[role_name]
140
+ user.groups.add(*self.get_groups(role_name))
141
+ user.user_permissions.add(*self.get_permissions(role_name))
142
+ if role.is_superuser != user.is_superuser:
143
+ user.is_superuser = role.is_superuser
144
+ user.save(update_fields=["is_superuser"])
145
+ break
146
+
147
+
148
+ role_handler = RoleHandler()
@@ -15,9 +15,9 @@ from requests.exceptions import HTTPError
15
15
 
16
16
  from django_jwt import settings as jwt_settings
17
17
  from django_jwt.config import config
18
- from django_jwt.exceptions import ConfigException
18
+ from django_jwt.exceptions import BadRequestException, ConfigException
19
19
  from django_jwt.pkce import PKCESecret
20
- from django_jwt.user import UserHandler
20
+ from django_jwt.user import UserHandler, role_handler
21
21
  from django_jwt.utils import get_access_token, oidc_handler
22
22
 
23
23
  log = getLogger(__name__)
@@ -36,6 +36,8 @@ class AbsView(View):
36
36
  return HttpResponse(status=exc.response.status_code, content=exc.response.text)
37
37
  except ConfigException as exc:
38
38
  return HttpResponse(content=str(exc), status=500)
39
+ except BadRequestException as exc:
40
+ return HttpResponse(content=str(exc), status=400)
39
41
  except Exception:
40
42
  return redirect("start_oidc_auth")
41
43
 
@@ -61,6 +63,7 @@ class StartOIDCAuthView(AbsView):
61
63
  "nonce": random_nonce,
62
64
  }
63
65
  cache.set(state, str(pkce_secret), timeout=600)
66
+ log.info(f"OIDC Admin login: {authorization_endpoint}?{urlencode(params)}")
64
67
  return redirect(f"{authorization_endpoint}?{urlencode(params)}")
65
68
 
66
69
 
@@ -70,15 +73,18 @@ class ReceiveRedirectView(AbsView):
70
73
  state = request.GET.get("state")
71
74
  if not code or not state:
72
75
  log.warning(f"No code or state in the request {request.GET}")
73
- return redirect("start_oidc_auth")
76
+ raise BadRequestException("No code or state in the request")
74
77
 
75
78
  redirect_uri = request.build_absolute_uri(reverse("receive_redirect_view"))
76
79
  if state := cache.get(state):
77
80
  token = get_access_token(code, redirect_uri, state)
78
81
  data = oidc_handler.decode_token(token)
79
82
  user = UserHandler(data, request, token).get_user()
80
- if user and user.is_staff:
81
- log.info(f"OIDC Admin login: {user}", extra={"data": data})
82
- login(request, user, backend=settings.DEFAULT_AUTHENTICATION_BACKEND)
83
- return redirect("admin:index")
84
- return redirect("start_oidc_auth")
83
+ log.info(f"OIDC Admin login: {user}", extra={"data": data})
84
+ role_handler.apply(user, data)
85
+ if not user.is_staff:
86
+ raise BadRequestException("User is not staff")
87
+ login(request, user, backend=settings.DEFAULT_AUTHENTICATION_BACKEND)
88
+ return redirect("admin:index")
89
+
90
+ raise BadRequestException("No PKCE secret found in cache")
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = dj-jwt-auth
3
- version = 1.2.0
3
+ version = 1.3.0
4
4
  description = A Django package for JSON Web Token validation and verification. Using PyJWT.
5
5
  long_description = file: README.md
6
6
  url = https://www.example.com/
@@ -2,12 +2,14 @@ from http import HTTPStatus
2
2
  from unittest.mock import Mock, patch
3
3
 
4
4
  from django.contrib.auth import get_user_model
5
+ from django.contrib.auth.models import Group, Permission
5
6
  from django.test import TestCase
6
7
  from django.urls import reverse
7
8
  from jwt.api_jwt import ExpiredSignatureError
8
9
 
9
10
  from django_jwt import settings
10
11
  from django_jwt.middleware import JWTAuthMiddleware
12
+ from django_jwt.user import ROLE, role_handler
11
13
 
12
14
  access_token_payload = {
13
15
  "sub": "12345",
@@ -102,13 +104,6 @@ class OIDCHandlerTest(TestCase):
102
104
  # fields are updated if they are changed in KeyCloak
103
105
  self.assertUserWithPayload()
104
106
 
105
- # def test_roles(self, decode_token):
106
- # """User has admin and staff roles"""
107
- # decode_token.return_value["realm_access"]["roles"] = ["admin", "staff"]
108
- # self.middleware.process_request(self.request)
109
- # self.assertTrue(self.request.user.is_staff)
110
- # self.assertTrue(self.request.user.is_superuser)
111
-
112
107
  def test_profile_info(self, *_):
113
108
  """User has profile info"""
114
109
 
@@ -140,3 +135,47 @@ class OIDCHandlerTest(TestCase):
140
135
  User.objects.create(kc_id="1234", first_name="", last_name="", username="")
141
136
  self.middleware.process_request(self.request)
142
137
  self.assertEqual(self.request.user.username, "on_update")
138
+
139
+
140
+ class RolesTest(TestCase):
141
+ def setUp(self) -> None:
142
+ self.user = User.objects.create(username="user")
143
+ settings.OIDC_ADMIN_ROLES = [
144
+ ROLE(
145
+ name="admin",
146
+ is_superuser=True,
147
+ ),
148
+ ROLE(
149
+ name="staff",
150
+ groups=["staff group"],
151
+ permissions=["add_user", "change_user", "delete_user"],
152
+ ),
153
+ ]
154
+ self.group = Group.objects.create(name="staff group")
155
+ self.permission = Permission.objects.get(name="Can add user")
156
+ self.access_token = {"resource_access": {settings.OIDC_ADMIN_CLIENT_ID: {"roles": ["staff"]}}}
157
+
158
+ def test_staff_role(self):
159
+ self.access_token["resource_access"][settings.OIDC_ADMIN_CLIENT_ID]["roles"] = ["staff"]
160
+ role_handler.apply(self.user, self.access_token)
161
+ self.assertTrue(self.user.groups.filter(name="staff group").exists())
162
+ self.assertTrue(self.user.user_permissions.filter(codename="add_user").exists())
163
+ self.assertFalse(self.user.is_superuser)
164
+
165
+ def test_admin_role(self):
166
+ self.access_token["resource_access"][settings.OIDC_ADMIN_CLIENT_ID]["roles"] = ["admin"]
167
+ role_handler.apply(self.user, self.access_token)
168
+ self.assertTrue(self.user.is_superuser)
169
+
170
+ def test_apply_staff_then_admin_role(self):
171
+ self.access_token["resource_access"][settings.OIDC_ADMIN_CLIENT_ID]["roles"] = ["staff"]
172
+ role_handler.apply(self.user, self.access_token)
173
+ self.assertFalse(self.user.is_superuser)
174
+ self.assertTrue(self.user.groups.filter(name="staff group").exists())
175
+ self.assertTrue(self.user.user_permissions.filter(codename="add_user").exists())
176
+
177
+ self.access_token["resource_access"][settings.OIDC_ADMIN_CLIENT_ID]["roles"] = ["admin"]
178
+ role_handler.apply(self.user, self.access_token)
179
+ self.assertTrue(self.user.is_superuser)
180
+ self.assertTrue(self.user.groups.filter(name="staff group").exists())
181
+ self.assertTrue(self.user.user_permissions.filter(codename="add_user").exists())
@@ -1,65 +0,0 @@
1
- # Django-JWT
2
-
3
- This is a package to verify and validate JSON Web Tokens (JWT) in Django.
4
-
5
- ### Installation
6
- 1. Install the package using pip.
7
-
8
- 2. Add "django_jwt" to your INSTALLED_APPS setting like this::
9
- ```
10
- INSTALLED_APPS = [
11
- ...
12
- 'django_jwt',
13
- ]
14
- ```
15
-
16
- 3. Add "django_jwt.middleware.JWTAuthMiddleware" to your MIDDLEWARE setting like this::
17
- ```
18
- MIDDLEWARE = [
19
- ...
20
- 'django_jwt.middleware.JWTAuthMiddleware',
21
- ]
22
- ```
23
-
24
- ### Configuration:
25
- Required variables:
26
- - OIDC_CERTS_URL - certificate endpoint, like `https://keyCloak/realms/h/.well-known/openid-configuration`
27
-
28
- Optional variables:
29
- - OIDC_AUDIENCE - by default ["account", "broker"]
30
- User retated variables:
31
- - OIDC_USER_UPDATE - if True, user model will be updated from userinfo endpoint if MODIFIED date has changed, by default True
32
- - OIDC_USER_MODIFIED_FIELD - user model field to store last modified date, by default `modified_timestamp`
33
- - OIDC_TOKEN_MODIFIED_FIELD - access token field to store last modified date, by default `updated_at`
34
- - OIDC_USER_UID - User model' unique identifier, by default `kc_id`
35
- - OIDC_USER_MAPPING - mapping between JWT claims and user model fields, by default:
36
- ```
37
- OIDC_USER_MAPPING = {
38
- 'first_name': 'first_name',
39
- 'last_name': 'last_name',
40
- 'username': 'username',
41
- }
42
- ```
43
- - OIDC_USER_DEFAULTS - default values for user model fields, by default:
44
- ```
45
- OIDC_USER_DEFAULTS = {
46
- 'is_active': True,
47
- }
48
- ```
49
-
50
- - OIDC_USER_ON_CREATE and OIDC_USER_ON_UPDATE - functions to be called on user creation and update, by default:
51
- ```
52
- OIDC_USER_ON_CREATE = None
53
- OIDC_USER_ON_UPDATE = None
54
- ```
55
- - OIDC_CONFIG_ROUTES - dict of 'algorithm': 'config_url', by default is empty. If filled will be used instead of OIDC_CERTS_URL
56
- ```
57
- OIDC_CONFIG_ROUTES = {
58
- 'RS256': 'https://keyCloak/realms/h/.well-known/openid-configuration',
59
- 'HS256': 'https://keyCloak/realms/h/.well-known/openid-configuration',
60
- }
61
- ```
62
- These functions should accept two arguments: user and request.
63
-
64
- ### Testing:
65
- Run command `python runtests.py` to run tests.
@@ -1,4 +0,0 @@
1
- class ConfigException(Exception):
2
- """Base class for exceptions in this module."""
3
-
4
- pass
File without changes
File without changes
File without changes
File without changes