pypomes-jwt 0.9.5__py3-none-any.whl → 0.9.6__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.
Potentially problematic release.
This version of pypomes-jwt might be problematic. Click here for more details.
- pypomes_jwt/jwt_pomes.py +15 -14
- pypomes_jwt/jwt_registry.py +43 -47
- {pypomes_jwt-0.9.5.dist-info → pypomes_jwt-0.9.6.dist-info}/METADATA +1 -1
- pypomes_jwt-0.9.6.dist-info/RECORD +8 -0
- pypomes_jwt-0.9.5.dist-info/RECORD +0 -8
- {pypomes_jwt-0.9.5.dist-info → pypomes_jwt-0.9.6.dist-info}/WHEEL +0 -0
- {pypomes_jwt-0.9.5.dist-info → pypomes_jwt-0.9.6.dist-info}/licenses/LICENSE +0 -0
pypomes_jwt/jwt_pomes.py
CHANGED
|
@@ -89,47 +89,48 @@ def jwt_assert_account(account_id: str) -> bool:
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def jwt_set_account(account_id: str,
|
|
92
|
-
reference_url: str,
|
|
93
92
|
claims: dict[str, Any],
|
|
94
93
|
access_max_age: int = JWT_ACCESS_MAX_AGE,
|
|
95
94
|
refresh_max_age: int = JWT_REFRESH_MAX_AGE,
|
|
96
95
|
grace_interval: int = None,
|
|
97
96
|
request_timeout: int = None,
|
|
98
|
-
|
|
97
|
+
remote_url: str = None,
|
|
99
98
|
logger: Logger = None) -> None:
|
|
100
99
|
"""
|
|
101
|
-
|
|
100
|
+
Establish the data needed to obtain JWT tokens for *account_id*.
|
|
102
101
|
|
|
103
102
|
:param account_id: the account identification
|
|
104
|
-
:param reference_url: the reference URL (for remote providers, URL to obtain and validate the JWT tokens)
|
|
105
103
|
:param claims: the JWT claimset, as key-value pairs
|
|
106
104
|
:param access_max_age: access token duration, in seconds
|
|
107
105
|
:param refresh_max_age: refresh token duration, in seconds
|
|
108
106
|
:param grace_interval: optional time to wait for token to be valid, in seconds
|
|
109
107
|
:param request_timeout: timeout for the requests to the reference URL
|
|
110
|
-
:param
|
|
108
|
+
:param remote_url: remote server's URL to obtain the JWT tokens from
|
|
111
109
|
:param logger: optional logger
|
|
112
110
|
"""
|
|
113
111
|
if logger:
|
|
114
112
|
logger.debug(msg=f"Register account data for '{account_id}'")
|
|
115
113
|
|
|
116
|
-
# extract the claims provided in the
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
# extract the claims provided in the remote_url URL's query string
|
|
115
|
+
if remote_url:
|
|
116
|
+
pos: int = remote_url.find("?")
|
|
117
|
+
if pos > 0:
|
|
118
|
+
claims = claims or {}
|
|
119
|
+
params: list[str] = remote_url[pos+1:].split(sep="&")
|
|
120
|
+
for param in params:
|
|
121
|
+
key: str = param.split("=")[0]
|
|
122
|
+
value: str = param.split("=")[1]
|
|
123
|
+
claims[key] = value
|
|
124
|
+
remote_url = remote_url[:pos]
|
|
123
125
|
|
|
124
126
|
# register the JWT service
|
|
125
127
|
__jwt_registry.add_account(account_id=account_id,
|
|
126
|
-
reference_url=reference_url,
|
|
127
128
|
claims=claims,
|
|
128
129
|
access_max_age=access_max_age,
|
|
129
130
|
refresh_max_age=refresh_max_age,
|
|
130
131
|
grace_interval=grace_interval,
|
|
131
132
|
request_timeout=request_timeout,
|
|
132
|
-
|
|
133
|
+
remote_url=remote_url,
|
|
133
134
|
logger=logger)
|
|
134
135
|
|
|
135
136
|
|
pypomes_jwt/jwt_registry.py
CHANGED
|
@@ -27,23 +27,22 @@ class JwtRegistry:
|
|
|
27
27
|
- access_data: dictionary holding the JWT token data, organized by account id:
|
|
28
28
|
{
|
|
29
29
|
<account-id>: {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"grace-interval": <int> # time to wait for token to be valid, in seconds
|
|
36
|
-
"request-timeout": <int> # timeout for the requests to the reference URL (in seconds)
|
|
30
|
+
"access-max-age": <int>, # defaults to JWT_ACCESS_MAX_AGE (in seconds)
|
|
31
|
+
"refresh-max-age": <int>, # defaults to JWT_REFRESH_MAX_AGE (in seconds)
|
|
32
|
+
"grace-interval": <int>, # time to wait for token to be valid, in seconds
|
|
33
|
+
"request-timeout": <int>, # timeout for the requests to the reference URL (in seconds)
|
|
34
|
+
"remote_url": <string>, # remote server's URL to obtain the JWT tokens from
|
|
37
35
|
"claims": {
|
|
38
|
-
"
|
|
39
|
-
"valid-until": <string> # token's finish (<YYYY-MM-DDThh:mm:ss+00:00>)
|
|
40
|
-
# optional
|
|
36
|
+
"iss": <string>, # token'ss issuer
|
|
41
37
|
"birthdate": <string>, # subject's birth date
|
|
42
38
|
"email": <string>, # subject's email
|
|
43
39
|
"gender": <string>, # subject's gender
|
|
44
40
|
"name": <string>, # subject's name
|
|
45
41
|
"roles": <List[str]>, # subject roles
|
|
46
42
|
"nonce": <string>, # used to associate a Client session with a token
|
|
43
|
+
# output, only
|
|
44
|
+
"valid-from": <string>, # token's start (<YYYY-MM-DDThh:mm:ss+00:00>)
|
|
45
|
+
"valid-until": <string>, # token's finish (<YYYY-MM-DDThh:mm:ss+00:00>)
|
|
47
46
|
...
|
|
48
47
|
}
|
|
49
48
|
},
|
|
@@ -58,30 +57,30 @@ class JwtRegistry:
|
|
|
58
57
|
|
|
59
58
|
Token-related claims are mostly required claims, and convey information about the token itself:
|
|
60
59
|
# required
|
|
61
|
-
"exp": <timestamp>
|
|
62
|
-
"iat": <timestamp>
|
|
63
|
-
"iss": <string>
|
|
64
|
-
"jti": <string>
|
|
65
|
-
"sub": <string>
|
|
60
|
+
"exp": <timestamp> expiration time
|
|
61
|
+
"iat": <timestamp> issued at
|
|
62
|
+
"iss": <string> token's issuer
|
|
63
|
+
"jti": <string> JWT id
|
|
64
|
+
"sub": <string> subject (the account identification)
|
|
66
65
|
# optional
|
|
67
|
-
"aud": <string>
|
|
68
|
-
"nbt": <timestamp>
|
|
66
|
+
"aud": <string> token audience
|
|
67
|
+
"nbt": <timestamp> not before time
|
|
69
68
|
|
|
70
69
|
Account-related claims are optional claims, and convey information about the registered account they belong to.
|
|
71
70
|
Alhough they can be freely specified, these are some of the most commonly used claims:
|
|
72
|
-
"valid-from": <string>
|
|
73
|
-
"valid-until": <string>
|
|
74
|
-
"birthdate": <string>
|
|
75
|
-
"email": <string>
|
|
76
|
-
"gender": <string>
|
|
77
|
-
"name": <string>
|
|
78
|
-
"roles": <List[str]>
|
|
79
|
-
"nonce": <string>
|
|
71
|
+
"valid-from": <string> token's start (<YYYY-MM-DDThh:mm:ss+00:00>)
|
|
72
|
+
"valid-until": <string> token's finish (<YYYY-MM-DDThh:mm:ss+00.00>)
|
|
73
|
+
"birthdate": <string> subject's birth date
|
|
74
|
+
"email": <string> subject's email
|
|
75
|
+
"gender": <string> subject's gender
|
|
76
|
+
"name": <string> subject's name
|
|
77
|
+
"roles": <List[str]> subject roles
|
|
78
|
+
"nonce": <string> used to associate a client session with a token
|
|
80
79
|
|
|
81
80
|
The token header has these items:
|
|
82
|
-
"alg": <string>
|
|
83
|
-
"typ": <string>
|
|
84
|
-
"kid": <string>
|
|
81
|
+
"alg": <string> the algorithm used to sign the token (one of *HS256*, *HS51*', *RSA256*, *RSA512*)
|
|
82
|
+
"typ": <string> the token type (fixed to *JWT*
|
|
83
|
+
"kid": <string> a reference to the token type and the key to its location in the token database
|
|
85
84
|
|
|
86
85
|
If issued by the local server, "kid" holds the key to the corresponding record in the token database,
|
|
87
86
|
if starting with *A* for (*Access*) or *R* (for *Refresh*), followed an integer.
|
|
@@ -95,13 +94,12 @@ class JwtRegistry:
|
|
|
95
94
|
|
|
96
95
|
def add_account(self,
|
|
97
96
|
account_id: str,
|
|
98
|
-
reference_url: str,
|
|
99
97
|
claims: dict[str, Any],
|
|
100
98
|
access_max_age: int,
|
|
101
99
|
refresh_max_age: int,
|
|
102
100
|
grace_interval: int | None,
|
|
103
101
|
request_timeout: int | None,
|
|
104
|
-
|
|
102
|
+
remote_url: str | None,
|
|
105
103
|
logger: Logger = None) -> None:
|
|
106
104
|
"""
|
|
107
105
|
Add to storage the parameters needed to produce and validate JWT tokens for *account_id*.
|
|
@@ -112,25 +110,23 @@ class JwtRegistry:
|
|
|
112
110
|
If the token provider is remote, all claims are sent to it at token request time.
|
|
113
111
|
|
|
114
112
|
:param account_id: the account identification
|
|
115
|
-
:param reference_url: the reference URL (for remote providers, URL to obtain and validate the JWT tokens)
|
|
116
113
|
:param claims: the JWT claimset, as key-value pairs
|
|
117
114
|
:param access_max_age: access token duration, in seconds
|
|
118
115
|
:param refresh_max_age: refresh token duration, in seconds
|
|
119
116
|
:param grace_interval: time to wait for token to be valid, in seconds
|
|
120
117
|
:param request_timeout: timeout for the requests to the reference URL (in seconds)
|
|
121
|
-
:param
|
|
118
|
+
:param remote_url: remote server's URL to obtain the JWT tokens from
|
|
122
119
|
:param logger: optional logger
|
|
123
120
|
"""
|
|
124
121
|
# build and store the access data for the account
|
|
125
122
|
with self.access_lock:
|
|
126
123
|
if account_id not in self.access_data:
|
|
127
124
|
self.access_data[account_id] = {
|
|
128
|
-
"reference-url": reference_url,
|
|
129
125
|
"access-max-age": access_max_age,
|
|
130
126
|
"refresh-max-age": refresh_max_age,
|
|
131
127
|
"grace-interval": grace_interval,
|
|
132
128
|
"request-timeout": request_timeout,
|
|
133
|
-
"remote-
|
|
129
|
+
"remote-url": remote_url,
|
|
134
130
|
"claims": claims or {}
|
|
135
131
|
}
|
|
136
132
|
if logger:
|
|
@@ -195,8 +191,6 @@ class JwtRegistry:
|
|
|
195
191
|
if not isinstance(nature, str) or \
|
|
196
192
|
len(nature) != 1 or nature < "A" or nature > "Z":
|
|
197
193
|
err_msg: str = f"Invalid nature '{nature}'"
|
|
198
|
-
elif not isinstance(claims, dict) or "iss" not in claims:
|
|
199
|
-
err_msg = f"invalid claims '{claims}'"
|
|
200
194
|
elif not isinstance(duration, int) or duration < 60:
|
|
201
195
|
err_msg = f"Invalid duration '{duration}'"
|
|
202
196
|
if err_msg:
|
|
@@ -209,11 +203,14 @@ class JwtRegistry:
|
|
|
209
203
|
logger=logger)
|
|
210
204
|
# issue the token
|
|
211
205
|
current_claims: dict[str, Any] = {}
|
|
206
|
+
iss: str = account_data["claims"].get("iss")
|
|
207
|
+
if iss:
|
|
208
|
+
current_claims["iss"] = iss
|
|
212
209
|
if claims:
|
|
213
210
|
current_claims.update(claims)
|
|
211
|
+
|
|
214
212
|
current_claims["jti"] = str_random(size=32,
|
|
215
213
|
chars=string.ascii_letters + string.digits)
|
|
216
|
-
current_claims["iss"] = account_data.get("reference-url")
|
|
217
214
|
current_claims["sub"] = account_id
|
|
218
215
|
just_now: int = int(datetime.now(tz=timezone.utc).timestamp())
|
|
219
216
|
current_claims["iat"] = just_now
|
|
@@ -240,7 +237,7 @@ class JwtRegistry:
|
|
|
240
237
|
"""
|
|
241
238
|
Issue and return a JWT token pair associated with *account_id*.
|
|
242
239
|
|
|
243
|
-
These claims are ignored, if specified in *account_claims*: *iat*, *
|
|
240
|
+
These claims are ignored, if specified in *account_claims*: *iat*, *exp*, *jti*, *nbf*, and *sub*.
|
|
244
241
|
Other claims specified therein may supercede registered account-related claims.
|
|
245
242
|
|
|
246
243
|
Structure of the return data:
|
|
@@ -264,20 +261,19 @@ class JwtRegistry:
|
|
|
264
261
|
with (self.access_lock):
|
|
265
262
|
account_data: dict[str, Any] = self.__get_account_data(account_id=account_id,
|
|
266
263
|
logger=logger)
|
|
267
|
-
current_claims: dict[str, Any] = account_data
|
|
264
|
+
current_claims: dict[str, Any] = account_data["claims"].copy()
|
|
268
265
|
if account_claims:
|
|
269
266
|
current_claims.update(account_claims)
|
|
270
267
|
current_claims["jti"] = str_random(size=32,
|
|
271
268
|
chars=string.ascii_letters + string.digits)
|
|
272
269
|
current_claims["sub"] = account_id
|
|
273
|
-
current_claims["iss"] = account_data.get("reference-url")
|
|
274
270
|
errors: list[str] = []
|
|
275
271
|
|
|
276
272
|
# where is the JWT service provider ?
|
|
277
|
-
if account_data.get("remote-
|
|
273
|
+
if account_data.get("remote-url"):
|
|
278
274
|
# JWT service is being provided by a remote server
|
|
279
275
|
result = _jwt_request_token(errors=errors,
|
|
280
|
-
|
|
276
|
+
remote_url=account_data.get("remote-url"),
|
|
281
277
|
claims=current_claims,
|
|
282
278
|
timeout=account_data.get("request-timeout"),
|
|
283
279
|
logger=logger)
|
|
@@ -371,7 +367,7 @@ class JwtRegistry:
|
|
|
371
367
|
|
|
372
368
|
|
|
373
369
|
def _jwt_request_token(errors: list[str],
|
|
374
|
-
|
|
370
|
+
remote_url: str,
|
|
375
371
|
claims: dict[str, Any],
|
|
376
372
|
timeout: int = None,
|
|
377
373
|
logger: Logger = None) -> dict[str, Any]:
|
|
@@ -389,7 +385,7 @@ def _jwt_request_token(errors: list[str],
|
|
|
389
385
|
of the provider issuing the JWT token.
|
|
390
386
|
|
|
391
387
|
:param errors: incidental errors
|
|
392
|
-
:param
|
|
388
|
+
:param remote_url: the reference URL for obtaining JWT tokens
|
|
393
389
|
:param claims: the JWT claimset, as expected by the issuing server
|
|
394
390
|
:param timeout: request timeout, in seconds (defaults to *None*)
|
|
395
391
|
:param logger: optional logger
|
|
@@ -399,9 +395,9 @@ def _jwt_request_token(errors: list[str],
|
|
|
399
395
|
|
|
400
396
|
# request the JWT token
|
|
401
397
|
if logger:
|
|
402
|
-
logger.debug(f"POST request JWT token to '{
|
|
398
|
+
logger.debug(f"POST request JWT token to '{remote_url}'")
|
|
403
399
|
response: Response = requests.post(
|
|
404
|
-
url=
|
|
400
|
+
url=remote_url,
|
|
405
401
|
json=claims,
|
|
406
402
|
timeout=timeout
|
|
407
403
|
)
|
|
@@ -414,7 +410,7 @@ def _jwt_request_token(errors: list[str],
|
|
|
414
410
|
logger.debug(f"JWT token obtained: {result}")
|
|
415
411
|
else:
|
|
416
412
|
# no, report the problem
|
|
417
|
-
err_msg: str = f"POST request to '{
|
|
413
|
+
err_msg: str = f"POST request to '{remote_url}' failed: {response.reason}"
|
|
418
414
|
if response.text:
|
|
419
415
|
err_msg += f" - {response.text}"
|
|
420
416
|
if logger:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_jwt
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.6
|
|
4
4
|
Summary: A collection of Python pomes, penyeach (JWT module)
|
|
5
5
|
Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-JWT
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-JWT/issues
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pypomes_jwt/__init__.py,sha256=t6TzpvttDuLMaKSGuBicOf9cZU4Y0N9mtby3ThS4lt8,1398
|
|
2
|
+
pypomes_jwt/jwt_constants.py,sha256=IQV39AiZKGuU8XxZBgJ-KJZQZ_mmnxyOnRZeuxlqDRk,4045
|
|
3
|
+
pypomes_jwt/jwt_pomes.py,sha256=--VxFAHVkYfc_SrVEHx9gqshpEzwfHk8LdG9_UZd3NE,20983
|
|
4
|
+
pypomes_jwt/jwt_registry.py,sha256=71E3_m773tTSLYTrCgouVhf58v_DqnHqgP3ALLbwxD0,25023
|
|
5
|
+
pypomes_jwt-0.9.6.dist-info/METADATA,sha256=J-6YPzBbIt1xpkHxZBUDrxEO13JTu2ILX7wrjhCdslw,632
|
|
6
|
+
pypomes_jwt-0.9.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
pypomes_jwt-0.9.6.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
+
pypomes_jwt-0.9.6.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pypomes_jwt/__init__.py,sha256=t6TzpvttDuLMaKSGuBicOf9cZU4Y0N9mtby3ThS4lt8,1398
|
|
2
|
-
pypomes_jwt/jwt_constants.py,sha256=IQV39AiZKGuU8XxZBgJ-KJZQZ_mmnxyOnRZeuxlqDRk,4045
|
|
3
|
-
pypomes_jwt/jwt_pomes.py,sha256=ZQ-x9nJqRqSfLXcoN0crh4a-BhT1MNOMvZkFTsaQsuE,21069
|
|
4
|
-
pypomes_jwt/jwt_registry.py,sha256=27Z0wbDCNcy_Klm50dGhJ1ZVYznj0SNdMjzHVT_Uzzo,25588
|
|
5
|
-
pypomes_jwt-0.9.5.dist-info/METADATA,sha256=IZT48rR9ftHECxA8Xy0HhhkHLX1rUQk-rDsdtMgb8TI,632
|
|
6
|
-
pypomes_jwt-0.9.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
-
pypomes_jwt-0.9.5.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
-
pypomes_jwt-0.9.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|