kinde-python-sdk 1.2.6__py3-none-any.whl → 1.2.8__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
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: kinde-python-sdk
3
- Version: 1.2.6
3
+ Version: 1.2.8
4
4
  Summary: Connect your app to the Kinde platform
5
5
  Author-email: Kinde Engineering <engineering@kinde.com>
6
6
  Project-URL: Homepage, https://github.com/kinde-oss/kinde-python-sdk
@@ -14,14 +14,14 @@ Classifier: Programming Language :: Python :: 3.9
14
14
  Requires-Python: >=3.8
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: urllib3 ~=2.2.1
18
- Requires-Dist: python-dateutil ~=2.9.0.post0
19
- Requires-Dist: Authlib ~=1.3.0
20
- Requires-Dist: pyjwt ~=2.8.0
21
- Requires-Dist: requests ~=2.31.0
22
- Requires-Dist: typing-extensions ~=4.11.0
23
- Requires-Dist: frozendict ~=2.4.3
24
- Requires-Dist: certifi ~=2024.2.2
17
+ Requires-Dist: urllib3
18
+ Requires-Dist: python-dateutil
19
+ Requires-Dist: Authlib
20
+ Requires-Dist: pyjwt
21
+ Requires-Dist: requests
22
+ Requires-Dist: typing-extensions>=4.11.0
23
+ Requires-Dist: frozendict>=2.4.3
24
+ Requires-Dist: certifi>=2024.7.4
25
25
 
26
26
  # Kinde Python SDK
27
27
 
@@ -2,7 +2,7 @@ kinde_sdk/__init__.py,sha256=cBbmyFIDIH3kIYStgbhPnRJgsfe1EGa9_kLnimT4m7k,726
2
2
  kinde_sdk/api_client.py,sha256=ofpdNwrQxXPFlv5KRisoPOXDta9K_Eg_IbD2hwLjAE8,58495
3
3
  kinde_sdk/configuration.py,sha256=oqZGvKEf4kNNURbI1pip2ZVVcDyRTSmmnjXWziH4s80,15765
4
4
  kinde_sdk/exceptions.py,sha256=1rFuvc5vj6ZjnT_m1yd_vCyFROfV_4ytcmFrj3rg1jw,4736
5
- kinde_sdk/kinde_api_client.py,sha256=pKOdksUbl4Y1O1cuQuYtVy66_PF_n2VUzS_ST1a9Ro0,12254
5
+ kinde_sdk/kinde_api_client.py,sha256=G5jZLahs3GqFFza5c-JnrXkBiwXFIaBWMUN5v6rH1gc,19160
6
6
  kinde_sdk/rest.py,sha256=HKfSGwlWNMRgB8wrT0u__2K-ZNFSSmAyBBFLH-dCilM,10552
7
7
  kinde_sdk/schemas.py,sha256=PC7NB7JscfXesOxq2TyddRb_3N2gqSZGUjsCKP3_7RU,97655
8
8
  kinde_sdk/apis/__init__.py,sha256=RTosXhMn41tMsKPUjIy-VK-_efOWzhkKiuGggJ3A6E0,214
@@ -509,7 +509,7 @@ kinde_sdk/paths/oauth2_v2_user_profile/__init__.py,sha256=O-78h6bM2O__SxkzxirG-q
509
509
  kinde_sdk/paths/oauth2_v2_user_profile/get.py,sha256=XDLPbzx2q_CkZ4Da5LJXGZBAySPxRrAoIYyilwedplA,8381
510
510
  kinde_sdk/paths/oauth2_v2_user_profile/get.pyi,sha256=aFY0cUUoAko2Gai-WIj4awMxCrHSjCMepztWdxwpdIA,8205
511
511
  kinde_sdk/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
512
- kinde_sdk/test/test_kinde_api_client.py,sha256=sNtWEXtnirtHeoZqI_svxaBLwTfExOwGYpf7VTrKOOo,9133
512
+ kinde_sdk/test/test_kinde_api_client.py,sha256=Tp-akbRcKcMMoCWWiSrXIwafC-aM-HdM-OhOFWjRuto,12764
513
513
  kinde_sdk/test/test_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
514
514
  kinde_sdk/test/test_models/test_add_organization_users_response.py,sha256=OxtCMFQ7WirbzdeBL0VXXcIBkrv_RXdr2jrbuhivzxU,1017
515
515
  kinde_sdk/test/test_models/test_api.py,sha256=5w0EcvDwo1tWfZ00TVNnyV79tW7z22H0DNfq6Rbswg8,517
@@ -582,8 +582,8 @@ kinde_sdk/test/test_models/test_user_profile_v2.py,sha256=QltYLnrMdUUM_rw6Fg5Nyw
582
582
  kinde_sdk/test/test_models/test_users.py,sha256=IGWziLNMAJbamUqoQxvcQKKd5G1D5Gp1sns9fdhxLt4,526
583
583
  kinde_sdk/test/test_models/test_users_response.py,sha256=ZHgkFH98bHU1JLIGh7R7MU4XfEqPY3QnkRiHZ2eLqmg,858
584
584
  kinde_sdk/test/test_models/test_webhook.py,sha256=9iZ6FjNBEA-rPbO1Deq0qonOkVp-ANoxFt0__O8COqg,533
585
- kinde_python_sdk-1.2.6.dist-info/LICENSE,sha256=iT6AIO6NJn_mo0kDD5mpz2zp9GpzH6YdhqOmkCBg-kQ,1385
586
- kinde_python_sdk-1.2.6.dist-info/METADATA,sha256=SZ5TCfTk8QxExDfLIlAcgjBNEjgx4KY4ovUeWaeymUc,2011
587
- kinde_python_sdk-1.2.6.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
588
- kinde_python_sdk-1.2.6.dist-info/top_level.txt,sha256=Tx4AaXvRSme43PIec8zPL_lCo02AWIRBG8JADU0GedQ,10
589
- kinde_python_sdk-1.2.6.dist-info/RECORD,,
585
+ kinde_python_sdk-1.2.8.dist-info/LICENSE,sha256=iT6AIO6NJn_mo0kDD5mpz2zp9GpzH6YdhqOmkCBg-kQ,1385
586
+ kinde_python_sdk-1.2.8.dist-info/METADATA,sha256=btNLaoqmgB2q-4M_mgeBriTd8v91h-37vt31BJiKde0,1961
587
+ kinde_python_sdk-1.2.8.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
588
+ kinde_python_sdk-1.2.8.dist-info/top_level.txt,sha256=Tx4AaXvRSme43PIec8zPL_lCo02AWIRBG8JADU0GedQ,10
589
+ kinde_python_sdk-1.2.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.3.0)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -133,6 +133,12 @@ class KindeApiClient(ApiClient):
133
133
  self._refresh_token()
134
134
  return True
135
135
  return False
136
+
137
+ def is_authenticated_token(self, token_value: dict) -> dict:
138
+ if token_value:
139
+ if token_value.is_expired():
140
+ return self._refresh_token_value(token_value)
141
+ return None
136
142
 
137
143
  def create_org(self) -> str:
138
144
  return f"{self.registration_url}&is_create_org=true"
@@ -145,23 +151,50 @@ class KindeApiClient(ApiClient):
145
151
  self._decode_token_if_needed(token_name)
146
152
  value = self.__decoded_tokens[token_name].get(key)
147
153
  return {"name": key, "value": value}
154
+
155
+ def get_claim_token(self, token_value: dict, key: str, token_name: str = "access_token") -> Any:
156
+ if token_name not in self.TOKEN_NAMES:
157
+ raise KindeTokenException(
158
+ f"Please use only tokens from the list: {self.TOKEN_NAMES}"
159
+ )
160
+
161
+ decoded_tokens = self._decode_token_if_needed_value(token_name,token_value)
162
+ value = decoded_tokens[token_name].get(key)
163
+ return {"name": key, "value": value}
148
164
 
149
165
  def get_permission(self, permission: str) -> Dict[str, Any]:
150
166
  return {
151
167
  "org_code": self.get_claim("org_code")["value"],
152
168
  "is_granted": permission in self.get_claim("permissions")["value"],
153
169
  }
170
+
171
+ def get_permission_token(self, token_value: dict, permission: str) -> Dict[str, Any]:
172
+ return {
173
+ "org_code": self.get_claim_token(token_value, "org_code")["value"],
174
+ "is_granted": permission in self.get_claim_token(token_value, "permissions")["value"],
175
+ }
154
176
 
155
177
  def get_permissions(self) -> Dict[str, Any]:
156
178
  return {
157
179
  "org_code": self.get_claim("org_code")["value"],
158
180
  "permissions": self.get_claim("permissions")["value"],
159
181
  }
182
+
183
+ def get_permissions_token(self, token_value: dict) -> Dict[str, Any]:
184
+ return {
185
+ "org_code": self.get_claim_token(token_value, "org_code")["value"],
186
+ "permissions": self.get_claim_token(token_value, "permissions")["value"],
187
+ }
160
188
 
161
189
  def get_organization(self) -> Dict[str, str]:
162
190
  return {
163
191
  "org_code": self.get_claim("org_code")["value"],
164
192
  }
193
+
194
+ def get_organization_token(self, token_value: dict) -> Dict[str, str]:
195
+ return {
196
+ "org_code": self.get_claim_token(token_value, "org_code")["value"],
197
+ }
165
198
 
166
199
  def get_user_details(self) -> Dict[str, str]:
167
200
  return {
@@ -171,11 +204,25 @@ class KindeApiClient(ApiClient):
171
204
  "email": self.get_claim("email", "id_token")["value"],
172
205
  "picture": self.get_claim("picture", "id_token")["value"],
173
206
  }
207
+
208
+ def get_user_details_token(self,token_value: dict) -> Dict[str, str]:
209
+ return {
210
+ "id": self.get_claim_token(token_value, "sub","id_token")["value"],
211
+ "given_name": self.get_claim_token(token_value, "given_name", "id_token")["value"],
212
+ "family_name": self.get_claim_token(token_value, "family_name", "id_token")["value"],
213
+ "email": self.get_claim_token(token_value, "email", "id_token")["value"],
214
+ "picture": self.get_claim_token(token_value, "picture", "id_token")["value"],
215
+ }
174
216
 
175
217
  def get_user_organizations(self) -> Dict[str, List[str]]:
176
218
  return {
177
219
  "org_codes": self.get_claim("org_codes", "id_token")["value"],
178
220
  }
221
+
222
+ def get_user_organizations_token(self, token_value: dict) -> Dict[str, List[str]]:
223
+ return {
224
+ "org_codes": self.get_claim_token(token_value, "org_codes", "id_token")["value"],
225
+ }
179
226
 
180
227
  def get_flag(
181
228
  self, code: str, default_value: Any = None, flag_type: str = ""
@@ -205,15 +252,53 @@ class KindeApiClient(ApiClient):
205
252
  result_flag["type"] = FlagType[flag_type].value
206
253
 
207
254
  return result_flag
255
+
256
+ def get_flag_token(
257
+ self, token_value: dict, code: str, default_value: Any = None, flag_type: str = ""
258
+ ) -> Any:
259
+ flags = self.get_claim_token(token_value, "feature_flags")["value"] or {}
260
+ flag = {}
261
+
262
+ if code not in list(flags.keys()):
263
+ if default_value is None:
264
+ raise KindeRetrieveException(
265
+ f"Flag {code} was not found, and no default value has been provided"
266
+ )
267
+ else:
268
+ flag = flags[code]
269
+ if flag_type and flag.get("t") and flag_type != flag.get("t"):
270
+ raise KindeRetrieveException(
271
+ f"Flag {code} is of type {FlagType[flag.get('t')].value} - requested type {FlagType[flag_type].value}"
272
+ )
273
+
274
+ result_flag = {
275
+ "code": code,
276
+ "value": flag.get("v") if flag else default_value,
277
+ "is_default": not bool(flag),
278
+ }
279
+ flag_type = flag["t"] if flag else flag_type
280
+ if flag_type:
281
+ result_flag["type"] = FlagType[flag_type].value
282
+
283
+ return result_flag
208
284
 
209
285
  def get_boolean_flag(self, code: str, default_value: Any = None) -> bool:
210
286
  return self.get_flag(code, default_value, "b")["value"]
287
+
288
+ def get_boolean_flag_token(self, token_value: dict, code: str, default_value: Any = None) -> bool:
289
+ return self.get_flag_token(token_value, code, default_value, "b")["value"]
211
290
 
212
291
  def get_string_flag(self, code: str, default_value: Any = None) -> str:
213
292
  return self.get_flag(code, default_value, "s")["value"]
293
+
294
+ def get_string_flag_token(self, token_value: dict, code: str, default_value: Any = None) -> str:
295
+ return self.get_flag_token(token_value, code, default_value, "s")["value"]
214
296
 
215
297
  def get_integer_flag(self, code: str, default_value: Any = None) -> int:
216
298
  return self.get_flag(code, default_value, "i")["value"]
299
+
300
+ def get_integer_flag_token(self, token_value: dict, code: str, default_value: Any = None) -> int:
301
+ return self.get_flag_token(token_value, code, default_value, "i")["value"]
217
302
 
218
303
  def call_api(self, *args, **kwargs) -> Any:
219
304
  self._get_or_refresh_access_token()
@@ -248,6 +333,34 @@ class KindeApiClient(ApiClient):
248
333
  self.__decoded_tokens[token_name] = jwt.decode(**decode_token_params)
249
334
  else:
250
335
  raise KindeTokenException(f"Token {token_name} doesn't exist.")
336
+
337
+ def _decode_token_if_needed_value(self, token_name: str, token_value: dict) -> dict:
338
+ if token_name not in token_value:
339
+ if not token_value:
340
+ raise KindeTokenException(
341
+ "Access token doesn't exist.\n"
342
+ "When grant_type is CLIENT_CREDENTIALS use fetch_token().\n"
343
+ 'For other grant_type use "get_login_url()" or "get_register_url()".'
344
+ )
345
+ token = token_value.get(token_name)
346
+
347
+ signing_key = self.jwks_client.get_signing_key_from_jwt(token)
348
+
349
+ if token:
350
+ decode_token_params = {
351
+ "jwt":token,
352
+ "key": signing_key.key,
353
+ "algorithms":["RS256"],
354
+ "options":{
355
+ "verify_signature": True,
356
+ "verify_exp": True,
357
+ "verify_aud": False
358
+ }
359
+ }
360
+ return jwt.decode(**decode_token_params)
361
+ else:
362
+ raise KindeTokenException(f"Token {token_name} doesn't exist.")
363
+ return token_value
251
364
 
252
365
  def fetch_token(self, authorization_response: Optional[str] = None) -> None:
253
366
  if self.grant_type == GrantType.CLIENT_CREDENTIALS:
@@ -274,6 +387,31 @@ class KindeApiClient(ApiClient):
274
387
  self.configuration.access_token = self.__access_token_obj.get("access_token")
275
388
  self._clear_decoded_tokens()
276
389
 
390
+ def fetch_token_value(self, authorization_response: Optional[str] = None) -> dict:
391
+ if self.grant_type == GrantType.CLIENT_CREDENTIALS:
392
+ params = {"grant_type": "client_credentials"}
393
+ if self.audience:
394
+ params["audience"] = self.audience
395
+ else:
396
+ if authorization_response is None:
397
+ raise KindeConfigurationException(
398
+ '"authorization_response" parameter is required when grant_type is different than CLIENT_CREDENTIALS.'
399
+ )
400
+ params = {"authorization_response": authorization_response}
401
+ if self.grant_type == GrantType.AUTHORIZATION_CODE_WITH_PKCE:
402
+ params["code_verifier"] = self.code_verifier
403
+
404
+ access_token_obj = self.client.fetch_token(
405
+ self.token_endpoint,
406
+ headers={
407
+ "Content-Type": "application/x-www-form-urlencoded",
408
+ "Kinde-SDK": "/".join(("Python", kinde_sdk_version)),
409
+ },
410
+ **params,
411
+ )
412
+ return access_token_obj
413
+
414
+
277
415
  def _get_or_refresh_access_token(self) -> None:
278
416
  if self.grant_type == GrantType.CLIENT_CREDENTIALS:
279
417
  if not self.__access_token_obj or self.__access_token_obj.is_expired():
@@ -309,6 +447,27 @@ class KindeApiClient(ApiClient):
309
447
  self._clear_decoded_tokens()
310
448
  else:
311
449
  raise KindeTokenException('"Access token" and "Refresh token" are invalid.')
450
+
451
+ def _refresh_token_value(self, token_value: dict) -> dict:
452
+ refresh_token = token_value.get("refresh_token")
453
+
454
+ if refresh_token:
455
+ token_value = self.client.refresh_token(
456
+ self.token_endpoint,
457
+ headers={
458
+ "Content-Type": "application/x-www-form-urlencoded",
459
+ "Kinde-SDK": "/".join(("Python", kinde_sdk_version)),
460
+ },
461
+ refresh_token=refresh_token,
462
+ )
463
+ if not token_value:
464
+ raise KindeTokenException(
465
+ '"Access token" and "Refresh token" are invalid.'
466
+ )
467
+
468
+ return token_value
469
+ else:
470
+ raise KindeTokenException('"Access token" and "Refresh token" are invalid.')
312
471
 
313
472
  def _add_additional_params(self, url: str, additional_params: Optional[Dict[str, str]] = None) -> str:
314
473
 
@@ -98,6 +98,12 @@ class TestKindeApiClient(unittest.TestCase):
98
98
  client.fetch_token(authorization_response="https://example.com/callback?code=test_code")
99
99
  self.mock_oauth2_session.return_value.fetch_token.assert_called_once()
100
100
 
101
+ def test_fetch_token_authorization_code_token(self):
102
+ client = self._create_kinde_client(GrantType.AUTHORIZATION_CODE)
103
+ self.mock_oauth2_session.return_value.fetch_token.return_value = {"access_token": "test_token"}
104
+ token_value = client.fetch_token_value(authorization_response="https://example.com/callback?code=test_code")
105
+ self.mock_oauth2_session.return_value.fetch_token.assert_called_once()
106
+
101
107
  @patch('kinde_sdk.kinde_api_client.ApiClient.call_api')
102
108
  def test_super_call_api_with_correct_args(self, mock_super_call_api):
103
109
  client = self._create_kinde_client(GrantType.CLIENT_CREDENTIALS)
@@ -142,6 +148,86 @@ class TestKindeApiClient(unittest.TestCase):
142
148
  mock_get_claim.assert_any_call("permissions")
143
149
  self.assertEqual(mock_get_claim.call_count, 2)
144
150
 
151
+ def test_get_permissions_token(self):
152
+ client = self._create_kinde_client(GrantType.AUTHORIZATION_CODE)
153
+
154
+ result = client.get_permissions_token({"access_token":{"org_code":"org123","permissions": ["read", "write", "delete"]}})
155
+
156
+ expected_result = {
157
+ "org_code": "org123",
158
+ "permissions": ["read", "write", "delete"]
159
+ }
160
+ self.assertEqual(result, expected_result)
161
+
162
+ def test_get_permission_token(self):
163
+ client = self._create_kinde_client(GrantType.AUTHORIZATION_CODE)
164
+
165
+ result = client.get_permission_token({"access_token":{"org_code":"org123","permissions": ["read", "write", "delete"]}},"read")
166
+
167
+ expected_result = {
168
+ "org_code": "org123",
169
+ "is_granted": True
170
+ }
171
+ self.assertEqual(result, expected_result)
172
+
173
+
174
+ def test_get_claim_token(self):
175
+ client = self._create_kinde_client(GrantType.AUTHORIZATION_CODE)
176
+
177
+ result = client.get_claim_token({"access_token":{"org_code":"org123","permissions": ["read", "write", "delete"]}},"org_code")
178
+
179
+ expected_result = {
180
+ "name": "org_code",
181
+ "value": "org123"
182
+ }
183
+ self.assertEqual(result, expected_result)
184
+
185
+ def test_get_user_details_token(self):
186
+ client = self._create_kinde_client(GrantType.AUTHORIZATION_CODE)
187
+
188
+ result = client.get_user_details_token({
189
+ "access_token":{"org_code":"org123","permissions": ["read", "write", "delete"]}
190
+ ,"id_token":{"sub":"123","given_name":"John","family_name":"Doe","email":"john@example.com","picture":"https://example.com/pic.jpg"}
191
+ })
192
+
193
+ expected_result = {
194
+ "id":"123","given_name":"John","family_name":"Doe","email":"john@example.com","picture":"https://example.com/pic.jpg"
195
+ }
196
+ self.assertEqual(result, expected_result)
197
+
198
+
199
+ def test_get_flag_token(self):
200
+ client = self._create_kinde_client(GrantType.AUTHORIZATION_CODE)
201
+
202
+ result = client.get_flag_token({
203
+ "access_token":{"org_code":"org123","permissions": ["read", "write", "delete"],"feature_flags":{"test_flag":{"v":True,"t":"b"}}}
204
+ ,"id_token":{"sub":"123","given_name":"John","family_name":"Doe","email":"john@example.com","picture":"https://example.com/pic.jpg"}
205
+ },
206
+ "test_flag"
207
+ )
208
+
209
+ expected_result = {
210
+ "code":"test_flag",
211
+ "value":True,
212
+ "is_default":False,
213
+ "type":"boolean"
214
+ }
215
+ self.assertEqual(result, expected_result)
216
+
217
+ def test_get_boolean_flag_token(self):
218
+ client = self._create_kinde_client(GrantType.AUTHORIZATION_CODE)
219
+
220
+ result = client.get_boolean_flag_token({
221
+ "access_token":{"org_code":"org123","permissions": ["read", "write", "delete"],"feature_flags":{"test_flag":{"v":True,"t":"b"}}}
222
+ ,"id_token":{"sub":"123","given_name":"John","family_name":"Doe","email":"john@example.com","picture":"https://example.com/pic.jpg"}
223
+ },
224
+ "test_flag"
225
+ )
226
+
227
+ expected_result = True
228
+ self.assertEqual(result, expected_result)
229
+
230
+
145
231
  def test_fetch_token_headers_with_authorization_code(self):
146
232
  client = self._create_kinde_client(GrantType.AUTHORIZATION_CODE)
147
233
  self.mock_oauth2_session.return_value.fetch_token.return_value = {"access_token": "test_token"}