kinde-python-sdk 1.2.7__py3-none-any.whl → 1.2.9__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.2
2
2
  Name: kinde-python-sdk
3
- Version: 1.2.7
3
+ Version: 1.2.9
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
@@ -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=FgFYIx46tjwFn4XmwqfDHzzM6Xt1dz_Y0rypsIumZGI,18818
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.7.dist-info/LICENSE,sha256=iT6AIO6NJn_mo0kDD5mpz2zp9GpzH6YdhqOmkCBg-kQ,1385
586
- kinde_python_sdk-1.2.7.dist-info/METADATA,sha256=RAjRR9bD1o_76KqsF9kMnmp9gbzWGn6z0du14KiVOUE,1961
587
- kinde_python_sdk-1.2.7.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
588
- kinde_python_sdk-1.2.7.dist-info/top_level.txt,sha256=Tx4AaXvRSme43PIec8zPL_lCo02AWIRBG8JADU0GedQ,10
589
- kinde_python_sdk-1.2.7.dist-info/RECORD,,
585
+ kinde_python_sdk-1.2.9.dist-info/LICENSE,sha256=iT6AIO6NJn_mo0kDD5mpz2zp9GpzH6YdhqOmkCBg-kQ,1385
586
+ kinde_python_sdk-1.2.9.dist-info/METADATA,sha256=hrkr9mMfiDDmQ_HfQAi80Y2qg3PyPPQ7m7Zb7xiWiI8,1961
587
+ kinde_python_sdk-1.2.9.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
588
+ kinde_python_sdk-1.2.9.dist-info/top_level.txt,sha256=Tx4AaXvRSme43PIec8zPL_lCo02AWIRBG8JADU0GedQ,10
589
+ kinde_python_sdk-1.2.9.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.1)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -133,6 +133,13 @@ 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
+ self._refresh_token_value(token_value)
141
+ return True
142
+ return None
136
143
 
137
144
  def create_org(self) -> str:
138
145
  return f"{self.registration_url}&is_create_org=true"
@@ -145,23 +152,50 @@ class KindeApiClient(ApiClient):
145
152
  self._decode_token_if_needed(token_name)
146
153
  value = self.__decoded_tokens[token_name].get(key)
147
154
  return {"name": key, "value": value}
155
+
156
+ def get_claim_token(self, token_value: dict, key: str, token_name: str = "access_token") -> Any:
157
+ if token_name not in self.TOKEN_NAMES:
158
+ raise KindeTokenException(
159
+ f"Please use only tokens from the list: {self.TOKEN_NAMES}"
160
+ )
161
+
162
+ decoded_tokens = self._decode_token_if_needed_value(token_name,token_value)
163
+ value = decoded_tokens[token_name].get(key)
164
+ return {"name": key, "value": value}
148
165
 
149
166
  def get_permission(self, permission: str) -> Dict[str, Any]:
150
167
  return {
151
168
  "org_code": self.get_claim("org_code")["value"],
152
169
  "is_granted": permission in self.get_claim("permissions")["value"],
153
170
  }
171
+
172
+ def get_permission_token(self, token_value: dict, permission: str) -> Dict[str, Any]:
173
+ return {
174
+ "org_code": self.get_claim_token(token_value, "org_code")["value"],
175
+ "is_granted": permission in self.get_claim_token(token_value, "permissions")["value"],
176
+ }
154
177
 
155
178
  def get_permissions(self) -> Dict[str, Any]:
156
179
  return {
157
180
  "org_code": self.get_claim("org_code")["value"],
158
181
  "permissions": self.get_claim("permissions")["value"],
159
182
  }
183
+
184
+ def get_permissions_token(self, token_value: dict) -> Dict[str, Any]:
185
+ return {
186
+ "org_code": self.get_claim_token(token_value, "org_code")["value"],
187
+ "permissions": self.get_claim_token(token_value, "permissions")["value"],
188
+ }
160
189
 
161
190
  def get_organization(self) -> Dict[str, str]:
162
191
  return {
163
192
  "org_code": self.get_claim("org_code")["value"],
164
193
  }
194
+
195
+ def get_organization_token(self, token_value: dict) -> Dict[str, str]:
196
+ return {
197
+ "org_code": self.get_claim_token(token_value, "org_code")["value"],
198
+ }
165
199
 
166
200
  def get_user_details(self) -> Dict[str, str]:
167
201
  return {
@@ -171,11 +205,25 @@ class KindeApiClient(ApiClient):
171
205
  "email": self.get_claim("email", "id_token")["value"],
172
206
  "picture": self.get_claim("picture", "id_token")["value"],
173
207
  }
208
+
209
+ def get_user_details_token(self,token_value: dict) -> Dict[str, str]:
210
+ return {
211
+ "id": self.get_claim_token(token_value, "sub","id_token")["value"],
212
+ "given_name": self.get_claim_token(token_value, "given_name", "id_token")["value"],
213
+ "family_name": self.get_claim_token(token_value, "family_name", "id_token")["value"],
214
+ "email": self.get_claim_token(token_value, "email", "id_token")["value"],
215
+ "picture": self.get_claim_token(token_value, "picture", "id_token")["value"],
216
+ }
174
217
 
175
218
  def get_user_organizations(self) -> Dict[str, List[str]]:
176
219
  return {
177
220
  "org_codes": self.get_claim("org_codes", "id_token")["value"],
178
221
  }
222
+
223
+ def get_user_organizations_token(self, token_value: dict) -> Dict[str, List[str]]:
224
+ return {
225
+ "org_codes": self.get_claim_token(token_value, "org_codes", "id_token")["value"],
226
+ }
179
227
 
180
228
  def get_flag(
181
229
  self, code: str, default_value: Any = None, flag_type: str = ""
@@ -205,15 +253,53 @@ class KindeApiClient(ApiClient):
205
253
  result_flag["type"] = FlagType[flag_type].value
206
254
 
207
255
  return result_flag
256
+
257
+ def get_flag_token(
258
+ self, token_value: dict, code: str, default_value: Any = None, flag_type: str = ""
259
+ ) -> Any:
260
+ flags = self.get_claim_token(token_value, "feature_flags")["value"] or {}
261
+ flag = {}
262
+
263
+ if code not in list(flags.keys()):
264
+ if default_value is None:
265
+ raise KindeRetrieveException(
266
+ f"Flag {code} was not found, and no default value has been provided"
267
+ )
268
+ else:
269
+ flag = flags[code]
270
+ if flag_type and flag.get("t") and flag_type != flag.get("t"):
271
+ raise KindeRetrieveException(
272
+ f"Flag {code} is of type {FlagType[flag.get('t')].value} - requested type {FlagType[flag_type].value}"
273
+ )
274
+
275
+ result_flag = {
276
+ "code": code,
277
+ "value": flag.get("v") if flag else default_value,
278
+ "is_default": not bool(flag),
279
+ }
280
+ flag_type = flag["t"] if flag else flag_type
281
+ if flag_type:
282
+ result_flag["type"] = FlagType[flag_type].value
283
+
284
+ return result_flag
208
285
 
209
286
  def get_boolean_flag(self, code: str, default_value: Any = None) -> bool:
210
287
  return self.get_flag(code, default_value, "b")["value"]
288
+
289
+ def get_boolean_flag_token(self, token_value: dict, code: str, default_value: Any = None) -> bool:
290
+ return self.get_flag_token(token_value, code, default_value, "b")["value"]
211
291
 
212
292
  def get_string_flag(self, code: str, default_value: Any = None) -> str:
213
293
  return self.get_flag(code, default_value, "s")["value"]
294
+
295
+ def get_string_flag_token(self, token_value: dict, code: str, default_value: Any = None) -> str:
296
+ return self.get_flag_token(token_value, code, default_value, "s")["value"]
214
297
 
215
298
  def get_integer_flag(self, code: str, default_value: Any = None) -> int:
216
299
  return self.get_flag(code, default_value, "i")["value"]
300
+
301
+ def get_integer_flag_token(self, token_value: dict, code: str, default_value: Any = None) -> int:
302
+ return self.get_flag_token(token_value, code, default_value, "i")["value"]
217
303
 
218
304
  def call_api(self, *args, **kwargs) -> Any:
219
305
  self._get_or_refresh_access_token()
@@ -248,6 +334,28 @@ class KindeApiClient(ApiClient):
248
334
  self.__decoded_tokens[token_name] = jwt.decode(**decode_token_params)
249
335
  else:
250
336
  raise KindeTokenException(f"Token {token_name} doesn't exist.")
337
+
338
+ def _decode_token_if_needed_value(self, token_name: str, token_value: dict) -> dict:
339
+ token = token_value.get(token_name)
340
+
341
+ if not isinstance(token, str):
342
+ return token_value
343
+
344
+ signing_key = self.jwks_client.get_signing_key_from_jwt(token)
345
+ if signing_key:
346
+ decode_token_params = {
347
+ "jwt":token,
348
+ "key": signing_key.key,
349
+ "algorithms":["RS256"],
350
+ "options":{
351
+ "verify_signature": True,
352
+ "verify_exp": True,
353
+ "verify_aud": False
354
+ }
355
+ }
356
+ return {token_name: jwt.decode(**decode_token_params)}
357
+ else:
358
+ raise KindeTokenException(f"Token {token_name} doesn't exist.")
251
359
 
252
360
  def fetch_token(self, authorization_response: Optional[str] = None) -> None:
253
361
  if self.grant_type == GrantType.CLIENT_CREDENTIALS:
@@ -274,6 +382,31 @@ class KindeApiClient(ApiClient):
274
382
  self.configuration.access_token = self.__access_token_obj.get("access_token")
275
383
  self._clear_decoded_tokens()
276
384
 
385
+ def fetch_token_value(self, authorization_response: Optional[str] = None) -> dict:
386
+ if self.grant_type == GrantType.CLIENT_CREDENTIALS:
387
+ params = {"grant_type": "client_credentials"}
388
+ if self.audience:
389
+ params["audience"] = self.audience
390
+ else:
391
+ if authorization_response is None:
392
+ raise KindeConfigurationException(
393
+ '"authorization_response" parameter is required when grant_type is different than CLIENT_CREDENTIALS.'
394
+ )
395
+ params = {"authorization_response": authorization_response}
396
+ if self.grant_type == GrantType.AUTHORIZATION_CODE_WITH_PKCE:
397
+ params["code_verifier"] = self.code_verifier
398
+
399
+ access_token_obj = self.client.fetch_token(
400
+ self.token_endpoint,
401
+ headers={
402
+ "Content-Type": "application/x-www-form-urlencoded",
403
+ "Kinde-SDK": "/".join(("Python", kinde_sdk_version)),
404
+ },
405
+ **params,
406
+ )
407
+ return access_token_obj
408
+
409
+
277
410
  def _get_or_refresh_access_token(self) -> None:
278
411
  if self.grant_type == GrantType.CLIENT_CREDENTIALS:
279
412
  if not self.__access_token_obj or self.__access_token_obj.is_expired():
@@ -309,6 +442,27 @@ class KindeApiClient(ApiClient):
309
442
  self._clear_decoded_tokens()
310
443
  else:
311
444
  raise KindeTokenException('"Access token" and "Refresh token" are invalid.')
445
+
446
+ def _refresh_token_value(self, token_value: dict) -> dict:
447
+ refresh_token = token_value.get("refresh_token")
448
+
449
+ if refresh_token:
450
+ token_value = self.client.refresh_token(
451
+ self.token_endpoint,
452
+ headers={
453
+ "Content-Type": "application/x-www-form-urlencoded",
454
+ "Kinde-SDK": "/".join(("Python", kinde_sdk_version)),
455
+ },
456
+ refresh_token=refresh_token,
457
+ )
458
+ if not token_value:
459
+ raise KindeTokenException(
460
+ '"Access token" and "Refresh token" are invalid.'
461
+ )
462
+
463
+ return token_value
464
+ else:
465
+ raise KindeTokenException('"Access token" and "Refresh token" are invalid.')
312
466
 
313
467
  def _add_additional_params(self, url: str, additional_params: Optional[Dict[str, str]] = None) -> str:
314
468
 
@@ -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"}