pypomes-jwt 0.9.5__py3-none-any.whl → 0.9.7__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/__init__.py CHANGED
@@ -4,7 +4,7 @@ from .jwt_constants import (
4
4
  JWT_DB_TABLE, JWT_DB_COL_KID, JWT_DB_COL_ACCOUNT,
5
5
  JWT_DB_COL_ALGORITHM, JWT_DB_COL_DECODER, JWT_DB_COL_TOKEN,
6
6
  JWT_ACCOUNT_LIMIT, JWT_ENCODING_KEY, JWT_DECODING_KEY,
7
- JWT_ACCESS_MAX_AGE, JWT_REFRESH_MAX_AGE
7
+ JWT_DEFAULT_ALGORITHM, JWT_ACCESS_MAX_AGE, JWT_REFRESH_MAX_AGE
8
8
  )
9
9
  from .jwt_pomes import (
10
10
  jwt_needed, jwt_verify_request,
@@ -20,7 +20,7 @@ __all__ = [
20
20
  "JWT_DB_TABLE", "JWT_DB_COL_KID", "JWT_DB_COL_ACCOUNT",
21
21
  "JWT_DB_COL_ALGORITHM", "JWT_DB_COL_DECODER", "JWT_DB_COL_TOKEN",
22
22
  "JWT_ACCOUNT_LIMIT", "JWT_ENCODING_KEY", "JWT_DECODING_KEY",
23
- "JWT_ACCESS_MAX_AGE", "JWT_REFRESH_MAX_AGE",
23
+ "JWT_DEFAULT_ALGORITHM", "JWT_ACCESS_MAX_AGE", "JWT_REFRESH_MAX_AGE",
24
24
  # jwt_pomes
25
25
  "jwt_needed", "jwt_verify_request",
26
26
  "jwt_assert_account", "jwt_set_account", "jwt_remove_account",
pypomes_jwt/jwt_pomes.py CHANGED
@@ -5,11 +5,11 @@ from logging import Logger
5
5
  from pypomes_db import db_select, db_delete
6
6
  from typing import Any
7
7
 
8
- from . import JWT_DB_COL_ACCOUNT
9
- from .jwt_constants import (
8
+ from . import (
10
9
  JWT_ACCESS_MAX_AGE, JWT_REFRESH_MAX_AGE,
11
10
  JWT_DEFAULT_ALGORITHM, JWT_DECODING_KEY,
12
- JWT_DB_TABLE, JWT_DB_COL_KID, JWT_DB_COL_ALGORITHM, JWT_DB_COL_DECODER
11
+ JWT_DB_TABLE, JWT_DB_COL_KID,
12
+ JWT_DB_COL_ACCOUNT, JWT_DB_COL_ALGORITHM, JWT_DB_COL_DECODER
13
13
  )
14
14
  from .jwt_registry import JwtRegistry
15
15
 
@@ -85,51 +85,39 @@ def jwt_assert_account(account_id: str) -> bool:
85
85
  :param account_id: the account identification
86
86
  :return: *True* if access data exists for *account_id*, *False* otherwise
87
87
  """
88
- return __jwt_registry.access_data.get(account_id) is not None
88
+ return __jwt_registry.access_registry.get(account_id) is not None
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
- request_timeout: int = None,
98
- remote_provider: bool = None,
99
96
  logger: Logger = None) -> None:
100
97
  """
101
- Set the data needed to obtain JWT tokens for *account_id*.
98
+ Establish the data needed to obtain JWT tokens for *account_id*.
99
+
100
+ The parameter *claims* may contain account-related claims, only. Ideally, it should contain,
101
+ at a minimum, *iss*, *birthdate*, *email*, *gender*, *name*, and *roles*.
102
+ It is enforced that the parameter *refresh_max_age* should be at least 300 seconds greater
103
+ than *access-max-age*.
102
104
 
103
105
  :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
106
  :param claims: the JWT claimset, as key-value pairs
106
107
  :param access_max_age: access token duration, in seconds
107
108
  :param refresh_max_age: refresh token duration, in seconds
108
109
  :param grace_interval: optional time to wait for token to be valid, in seconds
109
- :param request_timeout: timeout for the requests to the reference URL
110
- :param remote_provider: whether the JWT provider is a remote server
111
110
  :param logger: optional logger
112
111
  """
113
112
  if logger:
114
- logger.debug(msg=f"Register account data for '{account_id}'")
115
-
116
- # extract the claims provided in the reference URL's query string
117
- pos: int = reference_url.find("?")
118
- if pos > 0:
119
- params: list[str] = reference_url[pos+1:].split(sep="&")
120
- for param in params:
121
- claims[param.split("=")[0]] = param.split("=")[1]
122
- reference_url = reference_url[:pos]
113
+ logger.debug(msg=f"Registering account data for '{account_id}'")
123
114
 
124
115
  # register the JWT service
125
116
  __jwt_registry.add_account(account_id=account_id,
126
- reference_url=reference_url,
127
117
  claims=claims,
128
118
  access_max_age=access_max_age,
129
- refresh_max_age=refresh_max_age,
119
+ refresh_max_age=max(refresh_max_age, access_max_age + 300),
130
120
  grace_interval=grace_interval,
131
- request_timeout=request_timeout,
132
- remote_provider=remote_provider,
133
121
  logger=logger)
134
122
 
135
123
 
@@ -157,10 +145,12 @@ def jwt_validate_token(errors: list[str] | None,
157
145
  """
158
146
  Verify if *token* ia a valid JWT token.
159
147
 
160
- Raise an appropriate exception if validation failed.
161
- if *nature* is provided, verify whether *token* has been locally issued and is of a appropriate nature.
148
+ Raise an appropriate exception if validation failed. Attempt to validate non locally issued tokens
149
+ will not succeed. if *nature* is provided, validate whether *token* is of that nature.
162
150
  A token issued locally has the header claim *kid* starting with *A* (for *Access*) or *R* (for *Refresh*),
163
151
  followed by its id in the token database, or as a single letter in the range *[B-Z]*, less *R*.
152
+ If the *kid* claim contains such an id, then the cryptographic key needed for validation
153
+ will be obtained from the token database. Otherwise, the current decoding key is used.
164
154
 
165
155
  :param errors: incidental error messages
166
156
  :param token: the token to be validated
@@ -249,7 +239,7 @@ def jwt_validate_token(errors: list[str] | None,
249
239
 
250
240
  def jwt_revoke_token(errors: list[str] | None,
251
241
  account_id: str,
252
- refresh_token: str,
242
+ token: str,
253
243
  logger: Logger = None) -> bool:
254
244
  """
255
245
  Revoke the *refresh_token* associated with *account_id*.
@@ -258,7 +248,7 @@ def jwt_revoke_token(errors: list[str] | None,
258
248
 
259
249
  :param errors: incidental error messages
260
250
  :param account_id: the account identification
261
- :param refresh_token: the token to be revoked
251
+ :param token: the token to be revoked
262
252
  :param logger: optional logger
263
253
  :return: *True* if operation could be performed, *False* otherwise
264
254
  """
@@ -270,7 +260,7 @@ def jwt_revoke_token(errors: list[str] | None,
270
260
 
271
261
  op_errors: list[str] = []
272
262
  token_claims: dict[str, Any] = jwt_validate_token(errors=op_errors,
273
- token=refresh_token,
263
+ token=token,
274
264
  account_id=account_id,
275
265
  logger=logger)
276
266
  if not op_errors:
@@ -429,24 +419,26 @@ def jwt_refresh_tokens(errors: list[str] | None,
429
419
  logger.debug(msg=f"Refreshing a JWT token pair for '{account_id}'")
430
420
  op_errors: list[str] = []
431
421
 
432
- # verify whether this refresh token is legitimate
422
+ # assert the refresh token
433
423
  if refresh_token:
434
- account_claims: dict[str, Any] = (jwt_validate_token(errors=op_errors,
435
- token=refresh_token,
436
- nature="R",
437
- account_id=account_id,
438
- logger=logger) or {}).get("payload")
439
- # revoke current refresh token
440
- if account_claims and jwt_revoke_token(errors=errors,
424
+ # is the refresh token valid ?
425
+ account_claims = jwt_validate_token(errors=op_errors,
426
+ token=refresh_token,
427
+ nature="R",
428
+ account_id=account_id,
429
+ logger=logger)
430
+ # if it is, revoke current refresh token
431
+ if account_claims and jwt_revoke_token(errors=op_errors,
441
432
  account_id=account_id,
442
- refresh_token=refresh_token,
433
+ token=refresh_token,
443
434
  logger=logger):
444
435
  # issue tokens
445
- result = jwt_issue_tokens(errors=errors,
436
+ result = jwt_issue_tokens(errors=op_errors,
446
437
  account_id=account_id,
447
438
  account_claims=account_claims,
448
439
  logger=logger)
449
440
  else:
441
+ # refresh token not found
450
442
  op_errors.append("Refresh token was not provided")
451
443
 
452
444
  if op_errors:
@@ -460,17 +452,13 @@ def jwt_refresh_tokens(errors: list[str] | None,
460
452
 
461
453
  def jwt_get_claims(errors: list[str] | None,
462
454
  token: str,
463
- validate: bool = False,
464
455
  logger: Logger = None) -> dict[str, Any] | None:
465
456
  """
466
- Obtain and return the claims set of a JWT *token*.
457
+ Retrieve and return the claims set of a JWT *token*.
467
458
 
468
- If *validate* is set to *True*, tha following pieces of information are verified:
469
- - the token was issued and signed by the local provider, and is not corrupted
470
- - the claim 'exp' is present and is in the future
471
- - the claim 'nbf' is present and is in the past
459
+ Any valid JWT token may be provided in *token*, as this operation is not restricted to locally issued tokens.
472
460
 
473
- Structure of the returned data:
461
+ Structure of the returned data, for locally issued tokens:
474
462
  {
475
463
  "header": {
476
464
  "alg": "RS256",
@@ -484,7 +472,7 @@ def jwt_get_claims(errors: list[str] | None,
484
472
  "email": "jdoe@mail.com",
485
473
  "exp": 1516640454,
486
474
  "iat": 1516239022,
487
- "iss": "https://my_id_provider/issue",
475
+ "iss": "my_jwt_provider.com",
488
476
  "jti": "Uhsdfgr67FGH567qwSDF33er89retert",
489
477
  "gender": "M",
490
478
  "name": "John Doe",
@@ -499,7 +487,6 @@ def jwt_get_claims(errors: list[str] | None,
499
487
 
500
488
  :param errors: incidental error messages
501
489
  :param token: the token to be inspected for claims
502
- :param validate: If *True*, verifies the token's data (defaults to *False*)
503
490
  :param logger: optional logger
504
491
  :return: the token's claimset, or *None* if error
505
492
  """
@@ -510,19 +497,13 @@ def jwt_get_claims(errors: list[str] | None,
510
497
  logger.debug(msg="Retrieve claims for token")
511
498
 
512
499
  try:
513
- # retrieve the token's claims
514
- if validate:
515
- result = jwt_validate_token(errors=errors,
516
- token=token,
517
- logger=logger)
518
- else:
519
- header: dict[str, Any] = jwt.get_unverified_header(jwt=token)
520
- payload: dict[str, Any] = jwt.decode(jwt=token,
521
- options={"verify_signature": False})
522
- result = {
523
- "header": header,
524
- "payload": payload
525
- }
500
+ header: dict[str, Any] = jwt.get_unverified_header(jwt=token)
501
+ payload: dict[str, Any] = jwt.decode(jwt=token,
502
+ options={"verify_signature": False})
503
+ result = {
504
+ "header": header,
505
+ "payload": payload
506
+ }
526
507
  except Exception as e:
527
508
  if logger:
528
509
  logger.error(msg=str(e))
@@ -1,5 +1,4 @@
1
1
  import jwt
2
- import requests
3
2
  import string
4
3
  import sys
5
4
  from base64 import urlsafe_b64encode
@@ -7,12 +6,12 @@ from datetime import datetime, timezone
7
6
  from logging import Logger
8
7
  from pypomes_core import str_random
9
8
  from pypomes_db import db_connect, db_commit, db_update, db_delete
10
- from requests import Response
11
9
  from threading import Lock
12
10
  from typing import Any
13
11
 
14
- from .jwt_constants import (
15
- JWT_DEFAULT_ALGORITHM, JWT_ACCOUNT_LIMIT, JWT_ENCODING_KEY, JWT_DECODING_KEY,
12
+ from . import (
13
+ JWT_DEFAULT_ALGORITHM, JWT_ACCOUNT_LIMIT,
14
+ JWT_ENCODING_KEY, JWT_DECODING_KEY,
16
15
  JWT_DB_TABLE, JWT_DB_COL_KID, JWT_DB_COL_ACCOUNT,
17
16
  JWT_DB_COL_ALGORITHM, JWT_DB_COL_DECODER, JWT_DB_COL_TOKEN
18
17
  )
@@ -27,23 +26,20 @@ class JwtRegistry:
27
26
  - access_data: dictionary holding the JWT token data, organized by account id:
28
27
  {
29
28
  <account-id>: {
30
- "reference-url": # the reference URL
31
- "remote-provider": <bool>, # whether the JWT provider is a remote server
32
- "request-timeout": <int>, # in seconds - defaults to no timeout
33
- "access-max-age": <int>, # in seconds - defaults to JWT_ACCESS_MAX_AGE
34
- "refresh-max-age": <int>, # in seconds - defaults to JWT_REFRESH_MAX_AGE
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)
29
+ "access-max-age": <int>, # defaults to JWT_ACCESS_MAX_AGE (in seconds)
30
+ "refresh-max-age": <int>, # defaults to JWT_REFRESH_MAX_AGE (in seconds)
31
+ "grace-interval": <int>, # time to wait for token to be valid, in seconds
37
32
  "claims": {
38
- "valid-from": <string> # token's start (<YYYY-MM-DDThh:mm:ss+00:00>)
39
- "valid-until": <string> # token's finish (<YYYY-MM-DDThh:mm:ss+00:00>)
40
- # optional
41
- "birthdate": <string>, # subject's birth date
42
- "email": <string>, # subject's email
43
- "gender": <string>, # subject's gender
44
- "name": <string>, # subject's name
45
- "roles": <List[str]>, # subject roles
46
- "nonce": <string>, # used to associate a Client session with a token
33
+ "iss": <string>, # token'ss issuer
34
+ "birthdate": <string>, # subject's birth date
35
+ "email": <string>, # subject's email
36
+ "gender": <string>, # subject's gender
37
+ "name": <string>, # subject's name
38
+ "roles": <List[str]>, # subject roles
39
+ "nonce": <string>, # used to associate a Client session with a token
40
+ # output, only
41
+ "valid-from": <string>, # token's start (<YYYY-MM-DDThh:mm:ss+00:00>)
42
+ "valid-until": <string>, # token's finish (<YYYY-MM-DDThh:mm:ss+00:00>)
47
43
  ...
48
44
  }
49
45
  },
@@ -58,30 +54,30 @@ class JwtRegistry:
58
54
 
59
55
  Token-related claims are mostly required claims, and convey information about the token itself:
60
56
  # required
61
- "exp": <timestamp> # expiration time
62
- "iat": <timestamp> # issued at
63
- "iss": <string> # issuer (for remote providers, URL to obtain and validate the access tokens)
64
- "jti": <string> # JWT id
65
- "sub": <string> # subject (the account identification)
57
+ "exp": <timestamp> expiration time
58
+ "iat": <timestamp> issued at
59
+ "iss": <string> token's issuer
60
+ "jti": <string> JWT id
61
+ "sub": <string> subject (the account identification)
66
62
  # optional
67
- "aud": <string> # token audience
68
- "nbt": <timestamp> # not before time
63
+ "aud": <string> token audience
64
+ "nbt": <timestamp> not before time
69
65
 
70
66
  Account-related claims are optional claims, and convey information about the registered account they belong to.
71
67
  Alhough they can be freely specified, these are some of the most commonly used claims:
72
- "valid-from": <string> # token's start (<YYYY-MM-DDThh:mm:ss+00:00>)
73
- "valid-until": <string> # token's finish (<YYYY-MM-DDThh:mm:ss+00.00>)
74
- "birthdate": <string> # subject's birth date
75
- "email": <string> # subject's email
76
- "gender": <string> # subject's gender
77
- "name": <string> # subject's name
78
- "roles": <List[str]> # subject roles
79
- "nonce": <string> # used to associate a client session with a token
68
+ "valid-from": <string> token's start (<YYYY-MM-DDThh:mm:ss+00:00>)
69
+ "valid-until": <string> token's finish (<YYYY-MM-DDThh:mm:ss+00.00>)
70
+ "birthdate": <string> subject's birth date
71
+ "email": <string> subject's email
72
+ "gender": <string> subject's gender
73
+ "name": <string> subject's name
74
+ "roles": <List[str]> subject roles
75
+ "nonce": <string> used to associate a client session with a token
80
76
 
81
77
  The token header has these items:
82
- "alg": <string> # the algorithm used to sign the token (one of *HS256*, *HS51*', *RSA256*, *RSA512*)
83
- "typ": <string> # the token type (fixed to *JWT*
84
- "kid": <string> # a reference to the token type and the key to its location in the token database
78
+ "alg": <string> the algorithm used to sign the token (one of *HS256*, *HS51*', *RSA256*, *RSA512*)
79
+ "typ": <string> the token type (fixed to *JWT*)
80
+ "kid": <string> a token type and key to its location in the token database
85
81
 
86
82
  If issued by the local server, "kid" holds the key to the corresponding record in the token database,
87
83
  if starting with *A* for (*Access*) or *R* (for *Refresh*), followed an integer.
@@ -91,46 +87,36 @@ class JwtRegistry:
91
87
  Initizalize the token access data.
92
88
  """
93
89
  self.access_lock: Lock = Lock()
94
- self.access_data: dict[str, Any] = {}
90
+ self.access_registry: dict[str, Any] = {}
95
91
 
96
92
  def add_account(self,
97
93
  account_id: str,
98
- reference_url: str,
99
94
  claims: dict[str, Any],
100
95
  access_max_age: int,
101
96
  refresh_max_age: int,
102
97
  grace_interval: int | None,
103
- request_timeout: int | None,
104
- remote_provider: bool | None,
105
98
  logger: Logger = None) -> None:
106
99
  """
107
100
  Add to storage the parameters needed to produce and validate JWT tokens for *account_id*.
108
101
 
109
102
  The parameter *claims* may contain account-related claims, only. Ideally, it should contain,
110
- at a minimum, *birthdate*, *email*, *gender*, *name*, and *roles*.
111
- If the token provider is local, then the token-related claims are created at token issuing time.
112
- If the token provider is remote, all claims are sent to it at token request time.
103
+ at a minimum, *iss*, *birthdate*, *email*, *gender*, *name*, and *roles*.
104
+ The parameter *refresh_max_age* should be at least 300 seconds greater than *access-max-age*.
113
105
 
114
106
  :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
107
  :param claims: the JWT claimset, as key-value pairs
117
- :param access_max_age: access token duration, in seconds
118
- :param refresh_max_age: refresh token duration, in seconds
108
+ :param access_max_age: access token duration, in seconds (at least 60 seconds)
109
+ :param refresh_max_age: refresh token duration, in seconds (greater than *access_max_age*)
119
110
  :param grace_interval: time to wait for token to be valid, in seconds
120
- :param request_timeout: timeout for the requests to the reference URL (in seconds)
121
- :param remote_provider: whether the JWT provider is a remote server
122
111
  :param logger: optional logger
123
112
  """
124
113
  # build and store the access data for the account
125
114
  with self.access_lock:
126
- if account_id not in self.access_data:
127
- self.access_data[account_id] = {
128
- "reference-url": reference_url,
115
+ if account_id not in self.access_registry:
116
+ self.access_registry[account_id] = {
129
117
  "access-max-age": access_max_age,
130
118
  "refresh-max-age": refresh_max_age,
131
119
  "grace-interval": grace_interval,
132
- "request-timeout": request_timeout,
133
- "remote-provider": remote_provider,
134
120
  "claims": claims or {}
135
121
  }
136
122
  if logger:
@@ -151,7 +137,7 @@ class JwtRegistry:
151
137
  # remove from internal storage
152
138
  account_data: dict[str, Any] | None
153
139
  with self.access_lock:
154
- account_data = self.access_data.pop(account_id, None)
140
+ account_data = self.access_registry.pop(account_id, None)
155
141
 
156
142
  # remove from database
157
143
  db_delete(errors=None,
@@ -195,8 +181,6 @@ class JwtRegistry:
195
181
  if not isinstance(nature, str) or \
196
182
  len(nature) != 1 or nature < "A" or nature > "Z":
197
183
  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
184
  elif not isinstance(duration, int) or duration < 60:
201
185
  err_msg = f"Invalid duration '{duration}'"
202
186
  if err_msg:
@@ -209,11 +193,14 @@ class JwtRegistry:
209
193
  logger=logger)
210
194
  # issue the token
211
195
  current_claims: dict[str, Any] = {}
196
+ iss: str = account_data["claims"].get("iss")
197
+ if iss:
198
+ current_claims["iss"] = iss
212
199
  if claims:
213
200
  current_claims.update(claims)
201
+
214
202
  current_claims["jti"] = str_random(size=32,
215
203
  chars=string.ascii_letters + string.digits)
216
- current_claims["iss"] = account_data.get("reference-url")
217
204
  current_claims["sub"] = account_id
218
205
  just_now: int = int(datetime.now(tz=timezone.utc).timestamp())
219
206
  current_claims["iat"] = just_now
@@ -240,7 +227,7 @@ class JwtRegistry:
240
227
  """
241
228
  Issue and return a JWT token pair associated with *account_id*.
242
229
 
243
- These claims are ignored, if specified in *account_claims*: *iat*, *iss*, *exp*, *jti*, *nbf*, and *sub*.
230
+ These claims are ignored, if specified in *account_claims*: *iat*, *exp*, *jti*, *nbf*, and *sub*.
244
231
  Other claims specified therein may supercede registered account-related claims.
245
232
 
246
233
  Structure of the return data:
@@ -257,97 +244,79 @@ class JwtRegistry:
257
244
  :return: the JWT token data
258
245
  :raises RuntimeError: invalid account id, or error accessing the token database
259
246
  """
260
- # initialize the return variable
261
- result: dict[str, Any] | None = None
262
-
263
247
  # process the account data in storage
264
248
  with (self.access_lock):
265
249
  account_data: dict[str, Any] = self.__get_account_data(account_id=account_id,
266
250
  logger=logger)
267
- current_claims: dict[str, Any] = account_data.get("claims").copy()
251
+ current_claims: dict[str, Any] = account_data["claims"].copy()
268
252
  if account_claims:
269
253
  current_claims.update(account_claims)
270
254
  current_claims["jti"] = str_random(size=32,
271
255
  chars=string.ascii_letters + string.digits)
272
256
  current_claims["sub"] = account_id
273
- current_claims["iss"] = account_data.get("reference-url")
274
257
  errors: list[str] = []
275
258
 
276
- # where is the JWT service provider ?
277
- if account_data.get("remote-provider"):
278
- # JWT service is being provided by a remote server
279
- result = _jwt_request_token(errors=errors,
280
- reference_url=current_claims.get("iss"),
281
- claims=current_claims,
282
- timeout=account_data.get("request-timeout"),
283
- logger=logger)
284
- if errors:
285
- raise RuntimeError("; ".join(errors))
259
+ just_now: int = int(datetime.now(tz=timezone.utc).timestamp())
260
+ current_claims["iat"] = just_now
261
+ grace_interval = account_data.get("grace-interval")
262
+ if grace_interval:
263
+ current_claims["nbf"] = just_now + grace_interval
264
+ current_claims["valid-from"] = datetime.fromtimestamp(timestamp=current_claims["nbf"],
265
+ tz=timezone.utc).isoformat()
286
266
  else:
287
- # JWT service is being provided locally
288
- just_now: int = int(datetime.now(tz=timezone.utc).timestamp())
289
- current_claims["iat"] = just_now
290
- grace_interval = account_data.get("grace-interval")
291
- if grace_interval:
292
- current_claims["nbf"] = just_now + grace_interval
293
- current_claims["valid-from"] = datetime.fromtimestamp(timestamp=current_claims["nbf"],
294
- tz=timezone.utc).isoformat()
295
- else:
296
- current_claims["valid-from"] = datetime.fromtimestamp(timestamp=current_claims["iat"],
297
- tz=timezone.utc).isoformat()
298
- # issue a candidate refresh token first, and persist it
299
- current_claims["exp"] = just_now + account_data.get("refresh-max-age")
300
- current_claims["valid-until"] = datetime.fromtimestamp(timestamp=current_claims["exp"],
301
- tz=timezone.utc).isoformat()
302
- # may raise an exception
303
- refresh_token: str = jwt.encode(payload=current_claims,
304
- key=JWT_ENCODING_KEY,
305
- algorithm=JWT_DEFAULT_ALGORITHM,
306
- headers={"kid": "R0"})
307
- # obtain a DB connection (may raise an exception)
308
- db_conn: Any = db_connect(errors=errors,
309
- logger=logger)
310
- # persist the candidate token (may raise an exception)
311
- token_id: int = _jwt_persist_token(errors=errors,
312
- account_id=account_id,
313
- jwt_token=refresh_token,
314
- db_conn=db_conn,
315
- logger=logger)
316
- # issue the definitive refresh token
317
- refresh_token = jwt.encode(payload=current_claims,
267
+ current_claims["valid-from"] = datetime.fromtimestamp(timestamp=current_claims["iat"],
268
+ tz=timezone.utc).isoformat()
269
+ # issue a candidate refresh token first, and persist it
270
+ current_claims["exp"] = just_now + account_data.get("refresh-max-age")
271
+ current_claims["valid-until"] = datetime.fromtimestamp(timestamp=current_claims["exp"],
272
+ tz=timezone.utc).isoformat()
273
+ # may raise an exception
274
+ refresh_token: str = jwt.encode(payload=current_claims,
275
+ key=JWT_ENCODING_KEY,
276
+ algorithm=JWT_DEFAULT_ALGORITHM,
277
+ headers={"kid": "R0"})
278
+ # obtain a DB connection (may raise an exception)
279
+ db_conn: Any = db_connect(errors=errors,
280
+ logger=logger)
281
+ # persist the candidate token (may raise an exception)
282
+ token_id: int = _jwt_persist_token(errors=errors,
283
+ account_id=account_id,
284
+ jwt_token=refresh_token,
285
+ db_conn=db_conn,
286
+ logger=logger)
287
+ # issue the definitive refresh token
288
+ refresh_token = jwt.encode(payload=current_claims,
289
+ key=JWT_ENCODING_KEY,
290
+ algorithm=JWT_DEFAULT_ALGORITHM,
291
+ headers={"kid": f"R{token_id}"})
292
+ # persist it
293
+ db_update(errors=errors,
294
+ update_stmt=f"UPDATE {JWT_DB_TABLE}",
295
+ update_data={JWT_DB_COL_TOKEN: refresh_token},
296
+ where_data={JWT_DB_COL_KID: token_id},
297
+ connection=db_conn,
298
+ logger=logger)
299
+ # commit the transaction
300
+ db_commit(errors=errors,
301
+ connection=db_conn,
302
+ logger=logger)
303
+ if errors:
304
+ raise RuntimeError("; ".join(errors))
305
+
306
+ # issue the access token
307
+ current_claims["exp"] = just_now + account_data.get("access-max-age")
308
+ # may raise an exception
309
+ access_token: str = jwt.encode(payload=current_claims,
318
310
  key=JWT_ENCODING_KEY,
319
311
  algorithm=JWT_DEFAULT_ALGORITHM,
320
- headers={"kid": f"R{token_id}"})
321
- # persist it
322
- db_update(errors=errors,
323
- update_stmt=f"UPDATE {JWT_DB_TABLE}",
324
- update_data={JWT_DB_COL_TOKEN: refresh_token},
325
- where_data={JWT_DB_COL_KID: token_id},
326
- connection=db_conn,
327
- logger=logger)
328
- # commit the transaction
329
- db_commit(errors=errors,
330
- connection=db_conn,
331
- logger=logger)
332
- if errors:
333
- raise RuntimeError("; ".join(errors))
334
-
335
- # issue the access token
336
- current_claims["exp"] = just_now + account_data.get("access-max-age")
337
- # may raise an exception
338
- access_token: str = jwt.encode(payload=current_claims,
339
- key=JWT_ENCODING_KEY,
340
- algorithm=JWT_DEFAULT_ALGORITHM,
341
- headers={"kid": f"A{token_id}"})
342
- # return the token data
343
- result = {
344
- "access_token": access_token,
345
- "created_in": current_claims.get("iat"),
346
- "expires_in": current_claims.get("exp"),
347
- "refresh_token": refresh_token
348
- }
349
-
350
- return result
312
+ headers={"kid": f"A{token_id}"})
313
+ # return the token data
314
+ return {
315
+ "access_token": access_token,
316
+ "created_in": current_claims.get("iat"),
317
+ "expires_in": current_claims.get("exp"),
318
+ "refresh_token": refresh_token
319
+ }
351
320
 
352
321
  def __get_account_data(self,
353
322
  account_id: str,
@@ -359,7 +328,7 @@ class JwtRegistry:
359
328
  :raises RuntimeError: No JWT access data exists for *account_id*
360
329
  """
361
330
  # retrieve the access data
362
- result: dict[str, Any] = self.access_data.get(account_id)
331
+ result: dict[str, Any] = self.access_registry.get(account_id)
363
332
  if not result:
364
333
  # JWT access data not found
365
334
  err_msg: str = f"No JWT access data found for '{account_id}'"
@@ -370,60 +339,6 @@ class JwtRegistry:
370
339
  return result
371
340
 
372
341
 
373
- def _jwt_request_token(errors: list[str],
374
- reference_url: str,
375
- claims: dict[str, Any],
376
- timeout: int = None,
377
- logger: Logger = None) -> dict[str, Any]:
378
- """
379
- Obtain and return the JWT token from *reference_url*, along with its duration.
380
-
381
- Expected structure of the return data:
382
- {
383
- "access_token": <jwt-token>,
384
- "created_in": <timestamp>,
385
- "expires_in": <seconds-to-expiration>,
386
- "refresh_token": <token>
387
- }
388
- It is up to the invoker to make sure that the *claims* data conform to the requirements
389
- of the provider issuing the JWT token.
390
-
391
- :param errors: incidental errors
392
- :param reference_url: the reference URL for obtaining JWT tokens
393
- :param claims: the JWT claimset, as expected by the issuing server
394
- :param timeout: request timeout, in seconds (defaults to *None*)
395
- :param logger: optional logger
396
- """
397
- # initialize the return variable
398
- result: dict[str, Any] | None = None
399
-
400
- # request the JWT token
401
- if logger:
402
- logger.debug(f"POST request JWT token to '{reference_url}'")
403
- response: Response = requests.post(
404
- url=reference_url,
405
- json=claims,
406
- timeout=timeout
407
- )
408
-
409
- # was the request successful ?
410
- if response.status_code in [200, 201, 202]:
411
- # yes, save the access token data returned
412
- result = response.json()
413
- if logger:
414
- logger.debug(f"JWT token obtained: {result}")
415
- else:
416
- # no, report the problem
417
- err_msg: str = f"POST request to '{reference_url}' failed: {response.reason}"
418
- if response.text:
419
- err_msg += f" - {response.text}"
420
- if logger:
421
- logger.error(err_msg)
422
- errors.append(err_msg)
423
-
424
- return result
425
-
426
-
427
342
  def _jwt_persist_token(errors: list[str],
428
343
  account_id: str,
429
344
  jwt_token: str,
@@ -472,7 +387,6 @@ def _jwt_persist_token(errors: list[str],
472
387
  token_kid: int = rec[0]
473
388
  token_claims: dict[str, Any] = jwt_get_claims(errors=errors,
474
389
  token=token,
475
- validate=False,
476
390
  logger=logger)
477
391
  if errors:
478
392
  raise RuntimeError("; ".join(errors))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_jwt
3
- Version: 0.9.5
3
+ Version: 0.9.7
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=fLr_M8yXlcmSTNPMdJOJQlMmtaiK5YKh0vKjOp3z2E4,1446
2
+ pypomes_jwt/jwt_constants.py,sha256=IQV39AiZKGuU8XxZBgJ-KJZQZ_mmnxyOnRZeuxlqDRk,4045
3
+ pypomes_jwt/jwt_pomes.py,sha256=4-8qrfXt4y-auigVKPC_H6tTWBUZTjIPQM6yASnAgmc,20064
4
+ pypomes_jwt/jwt_registry.py,sha256=UgJ8eEE8odXUE57yXeozoHJjBDmNVZQCs4qee5IARJ8,21425
5
+ pypomes_jwt-0.9.7.dist-info/METADATA,sha256=SXGugukd0a4ghPNchPAL563zbxau5dggEjalvq7G0Wc,632
6
+ pypomes_jwt-0.9.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ pypomes_jwt-0.9.7.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
8
+ pypomes_jwt-0.9.7.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,,