dj-jwt-auth 1.7.1__py3-none-any.whl → 1.8.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dj-jwt-auth
3
- Version: 1.7.1
3
+ Version: 1.8.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
@@ -137,3 +137,4 @@ Login URL will be available at `/admin/oidc/`.
137
137
 
138
138
  ### Testing:
139
139
  Run command `python runtests.py` to run tests.
140
+ To run specific test use `python runtests.py <test_name>`, like `python runtests.py "tests.test.OIDCHandlerTest.test_new_email_exists"`.
@@ -6,16 +6,16 @@ django_jwt/pkce.py,sha256=j-v2ffCw0X3JW7ak8vfNeSZI-dACOvHbi1eLmJ0R8gM,685
6
6
  django_jwt/roles.py,sha256=SaHK3o8T8USS4ZhG4SrHPlZQV2lMb2t1UZHT6IQtBvA,143
7
7
  django_jwt/settings.py,sha256=pXQ8WUU4LGBe6PQxCLTLM_2_b1CCSgehqim3yJDqZdw,1922
8
8
  django_jwt/urls.py,sha256=ZhcnRcQ1MBRh-bS7fTa-Vkz8yuWUhv-G_uRXKLnKAs0,320
9
- django_jwt/user.py,sha256=e1OizNlxUQjH2jwbbFxHN0go0pJiVrmLJUDP7lBuUUM,5859
9
+ django_jwt/user.py,sha256=v2oCoAThstFg5x5cOA9nSKlYWZzCEqhFbz63WGhE_NM,6382
10
10
  django_jwt/utils.py,sha256=uIFw1JMldE_blC7YkaZbY7rpcawP2E6zsdLyuZRwJoA,1645
11
11
  django_jwt/views.py,sha256=jQW3YrW-Oed54KqxjDMaBOQ-dxEnqTAcBEXlQSK-zCE,4231
12
12
  django_jwt/templates/django-jwt-index.html,sha256=y8f0v2WbRAFxnIU799I_MZCVsjn1sbdh7bypjdWB0lA,1353
13
13
  django_jwt/templates/admin/login.html,sha256=Nihyu0IGvDDZVvQDITXozwlj6XCQ0B8gqlyHLqVNyJc,275
14
14
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- tests/models.py,sha256=tJlhRARVGfFIH6Gp1o00buZKcY2oAk7HNw2PMnkTEDs,295
16
- tests/test.py,sha256=Llj8u4hyxezdUdCOnM5ogw_hLxC82tiSY6e14W42GjU,11089
15
+ tests/models.py,sha256=jhoJcCEsx5B9AibmoLQLDD3cEsyYzYy6dMdYhRnBVFo,338
16
+ tests/test.py,sha256=HEy5DsEYrPmtWSdeDhgdeV2tZTYlepaWt82VoTBrta8,11828
17
17
  tests/urls.py,sha256=D5FhDSVAudurkrpkCZZPnDvgXSgifwFVB3nAlYBg7uQ,212
18
- dj_jwt_auth-1.7.1.dist-info/METADATA,sha256=AxY9nhrLPx8qkqcvIWfXe1TJSsjZ95d4cjFwfSCJ8zM,4416
19
- dj_jwt_auth-1.7.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
20
- dj_jwt_auth-1.7.1.dist-info/top_level.txt,sha256=58O7TdK-yECZcbmPc52KNlBFpjIUlENuZubCxaSOxus,17
21
- dj_jwt_auth-1.7.1.dist-info/RECORD,,
18
+ dj_jwt_auth-1.8.0.dist-info/METADATA,sha256=bSdoEA3Rd_Ug7C7B4ZaPCnBs4WOnLUjGhVNkMjDGxMU,4553
19
+ dj_jwt_auth-1.8.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
20
+ dj_jwt_auth-1.8.0.dist-info/top_level.txt,sha256=58O7TdK-yECZcbmPc52KNlBFpjIUlENuZubCxaSOxus,17
21
+ dj_jwt_auth-1.8.0.dist-info/RECORD,,
django_jwt/user.py CHANGED
@@ -4,6 +4,8 @@ from logging import getLogger
4
4
 
5
5
  from django.contrib.auth import get_user_model
6
6
  from django.contrib.auth.models import Group, Permission
7
+ from django.db import transaction
8
+ from django.db.utils import IntegrityError
7
9
  from django.http.request import HttpRequest
8
10
 
9
11
  from django_jwt import settings
@@ -23,6 +25,7 @@ def mapper(user_data: dict) -> dict:
23
25
 
24
26
  class UserHandler:
25
27
  modified_at = None
28
+ userdata_collected = False
26
29
 
27
30
  def __init__(self, payload: dict, request: HttpRequest, access_token: str):
28
31
  self.payload = payload
@@ -41,10 +44,12 @@ class UserHandler:
41
44
  def _collect_user_data(self):
42
45
  """Collect user data from KeyCloak"""
43
46
 
44
- user_data = oidc_handler.get_user_info(self.access_token)
45
- log.info(f"[OIDC] User data: {user_data}")
46
- self.kwargs["email"] = user_data["email"].lower()
47
- self.kwargs.update(mapper(user_data))
47
+ if not self.userdata_collected:
48
+ user_data = oidc_handler.get_user_info(self.access_token)
49
+ log.debug(f"[OIDC] User data: {user_data}")
50
+ self.kwargs["email"] = user_data["email"].lower()
51
+ self.kwargs.update(mapper(user_data))
52
+ self.userdata_collected = True
48
53
 
49
54
  def _update_user(self, user):
50
55
  """Update user fields if they are changed"""
@@ -69,6 +74,7 @@ class UserHandler:
69
74
  def _get_by_email(self) -> model:
70
75
  """Get user from database by email and update to resave kc_id."""
71
76
 
77
+ self._collect_user_data()
72
78
  user = model.objects.get(email=self.kwargs["email"])
73
79
  self._update_user(user)
74
80
  if self.on_update:
@@ -84,17 +90,16 @@ class UserHandler:
84
90
  user_modified_at = user_modified_at.replace(tzinfo=utc)
85
91
  is_modified = user_modified_at < self.modified_at
86
92
 
87
- log.info(
88
- f"[OIDC] User modified at: {user_modified_at}, "
89
- f"modified_at: {self.modified_at}, "
90
- f"is_modified: {is_modified}, "
91
- f"email: {user.email}",
92
- )
93
93
  if self.modified_at and is_modified:
94
94
  self._update_user(user)
95
95
  if self.on_update:
96
96
  self.on_update(user, self.request, self.payload)
97
97
 
98
+ def _clean_kc_id(self):
99
+ model.objects.filter(**{settings.OIDC_USER_UID: self.kwargs[settings.OIDC_USER_UID]}).update(
100
+ **{settings.OIDC_USER_UID: None}
101
+ )
102
+
98
103
  def get_user(self) -> model:
99
104
  """
100
105
  Get user from database by kc_id or email.
@@ -103,18 +108,29 @@ class UserHandler:
103
108
  """
104
109
 
105
110
  try:
106
- user = model.objects.get(**{settings.OIDC_USER_UID: self.kwargs[settings.OIDC_USER_UID]})
107
- self._update_existing_user(user)
111
+ with transaction.atomic():
112
+ user = model.objects.get(**{settings.OIDC_USER_UID: self.kwargs[settings.OIDC_USER_UID]})
113
+ self._update_existing_user(user)
108
114
  return user
109
115
 
116
+ except IntegrityError:
117
+ # User with this email already exists
118
+ self._clean_kc_id()
119
+ return self._get_by_email() # Will update kc_id
120
+
110
121
  except model.DoesNotExist:
111
122
  self._collect_user_data()
112
123
  try:
113
124
  return self._get_by_email()
114
125
  except model.DoesNotExist:
115
126
  return self._create_new_user()
127
+
116
128
  except model.MultipleObjectsReturned:
117
- log.error(f"[OIDC] Multiple users found by {settings.OIDC_USER_UID}: {self.kwargs[settings.OIDC_USER_UID]}")
129
+ log.warning(
130
+ f"[OIDC] Multiple users found by {settings.OIDC_USER_UID}: {self.kwargs[settings.OIDC_USER_UID]}"
131
+ )
132
+ # clear kc_id if multiple users found
133
+ self._clean_kc_id()
118
134
  return self._get_by_email()
119
135
 
120
136
 
tests/models.py CHANGED
@@ -6,3 +6,4 @@ from django.utils import timezone
6
6
  class User(AbstractUser):
7
7
  kc_id = models.CharField(max_length=255, null=True, blank=True)
8
8
  modified_timestamp = models.DateTimeField(auto_now=False, default=timezone.now)
9
+ email = models.EmailField(unique=True)
tests/test.py CHANGED
@@ -114,14 +114,36 @@ class OIDCHandlerTest(TestCase):
114
114
  self.assertUserWithPayload()
115
115
 
116
116
  def test_exists_multiple_kc_id(self, *_):
117
- """User exists in database by email"""
118
- user = User.objects.create(email="example@bk.com", kc_id="123")
119
- User.objects.create(email="example@gmail.com", kc_id="123", username="other")
117
+ """
118
+ KC ID exists in database by email
119
+ """
120
+
121
+ user = User.objects.create(email="example@bk.com", kc_id="1234")
122
+ User.objects.create(email="example@gmail.com", kc_id="1234", username="other")
123
+
120
124
  self.middleware.process_request(self.request)
125
+
121
126
  self.assertEqual(self.request.user, user)
127
+ self.assertUserWithPayload()
122
128
 
123
- # fields are updated if they are changed in KeyCloak
129
+ def test_new_email_exists(self, user_info, access_token):
130
+ """Test case when:
131
+ - some email 'A' exists in DB with some KC ID
132
+ - user change email in KC from 'B' to 'A'
133
+ - KC ID will be attached to existing user with email 'A'
134
+ """
135
+ user_info.return_value["email"] = "a@bk.com"
136
+ user_a = User.objects.create(email="a@bk.com", username="a")
137
+ user_b = User.objects.create(email="b@bk.com", kc_id="1234", username="b")
138
+
139
+ self.middleware.process_request(self.request)
140
+ user_a.refresh_from_db()
141
+ user_b.refresh_from_db()
142
+
143
+ self.assertEqual(self.request.user, user_a)
124
144
  self.assertUserWithPayload()
145
+ self.assertEqual(user_a.kc_id, "1234")
146
+ self.assertEqual(user_b.kc_id, None)
125
147
 
126
148
  def test_exists_email_differeent_kc_id_user(self, *_):
127
149
  """User exists in database by email but different kc_id"""
@@ -180,7 +202,7 @@ class OIDCHandlerTest(TestCase):
180
202
  self.middleware.process_request(self.request)
181
203
  self.assertEqual(self.request.user.username, "override")
182
204
 
183
- def test_updated_at(self, access_token, user_info):
205
+ def test_updated_at(self, user_info, access_token):
184
206
  """Check that
185
207
  - the updated_at field saved correct
186
208
  - don't call userdata if updated_at is not changed
@@ -199,7 +221,7 @@ class OIDCHandlerTest(TestCase):
199
221
  self.middleware.process_request(self.request)
200
222
  user.refresh_from_db()
201
223
  self.assertEqual(user.modified_timestamp, updated_at)
202
- # self.assertEqual(user_info.call_count, 1)
224
+ self.assertEqual(user_info.call_count, 1)
203
225
 
204
226
 
205
227
  @patch("django_jwt.utils.get_alg", return_value="HS256")