pypomes-jwt 1.3.7__tar.gz → 1.3.9__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_jwt
3
- Version: 1.3.7
3
+ Version: 1.3.9
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
@@ -10,7 +10,8 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.12
13
- Requires-Dist: cryptography>=46.0.2
13
+ Requires-Dist: cryptography>=46.0.3
14
14
  Requires-Dist: flask>=3.1.2
15
15
  Requires-Dist: pyjwt>=2.10.1
16
- Requires-Dist: pypomes-core>=2.7.6
16
+ Requires-Dist: pypomes-core>=2.7.8
17
+ Requires-Dist: pypomes-db>=2.8.1
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "pypomes_jwt"
9
- version = "1.3.7"
9
+ version = "1.3.9"
10
10
  authors = [
11
11
  { name="GT Nunes", email="wisecoder01@gmail.com" }
12
12
  ]
@@ -19,11 +19,11 @@ classifiers = [
19
19
  "Operating System :: OS Independent"
20
20
  ]
21
21
  dependencies = [
22
- "cryptography>=46.0.2",
22
+ "cryptography>=46.0.3",
23
23
  "Flask>=3.1.2",
24
24
  "PyJWT>=2.10.1",
25
- "pypomes_core>=2.7.6"
26
- # "pypomes_db>=2.7.6"
25
+ "pypomes_core>=2.7.8",
26
+ "pypomes_db>=2.8.1"
27
27
  ]
28
28
 
29
29
  [project.urls]
@@ -7,20 +7,15 @@ from .jwt_pomes import (
7
7
  jwt_issue_token, jwt_issue_tokens, jwt_refresh_tokens,
8
8
  jwt_get_claims, jwt_validate_token, jwt_revoke_token
9
9
  )
10
- from .jwt_providers import (
11
- provider_register, provider_get_token
12
- )
13
10
 
14
11
  __all__ = [
15
- # jwt_constants
12
+ # jwt_config
16
13
  "JwtConfig", "JwtDbConfig", "JwtAlgorithm",
17
14
  # jwt_pomes
18
15
  "jwt_needed", "jwt_verify_request",
19
16
  "jwt_assert_account", "jwt_set_account", "jwt_remove_account",
20
17
  "jwt_issue_token", "jwt_issue_tokens", "jwt_refresh_tokens",
21
- "jwt_get_claims", "jwt_validate_token", "jwt_revoke_token",
22
- # jwt_providers
23
- "provider_register", "provider_get_token"
18
+ "jwt_get_claims", "jwt_validate_token", "jwt_revoke_token"
24
19
  ]
25
20
 
26
21
  from importlib.metadata import version
@@ -66,7 +66,7 @@ del _encoding_key
66
66
  del _default_algorithm
67
67
 
68
68
 
69
- # database access is not be necessary, if handling only externally provided JWT tokens
69
+ # database access is not be necessary, if only handling externally provided JWT tokens
70
70
  class JwtDbConfig(StrEnum):
71
71
  """
72
72
  Parameters for JWT database connection.
@@ -52,7 +52,7 @@ def jwt_verify_request(request: Request) -> Response:
52
52
  # validate the authorization token
53
53
  bad_token: bool = True
54
54
  if auth_header and auth_header.startswith("Bearer "):
55
- # yes, extract and validate the JWT access token
55
+ # extract and validate the JWT access token
56
56
  token: str = auth_header.split(" ")[1]
57
57
  claims: dict[str, Any] = jwt_validate_token(token=token,
58
58
  nature="A")
@@ -224,14 +224,17 @@ def jwt_validate_token(token: str,
224
224
  # InvalidIssuedAtError: 'iat' claim is non-numeric
225
225
  # MissingRequiredClaimError: a required claim is not contained in the claimset
226
226
  payload: dict[str, Any] = jwt.decode(jwt=token,
227
+ key=token_decoder,
228
+ algorithms=token_alg,
227
229
  options={
228
- "verify_signature": True,
230
+ "require": ["iat", "iss", "exp", "sub"],
231
+ "verify_aud": False,
229
232
  "verify_exp": True,
230
- "verify_nbf": True
231
- },
232
- key=token_decoder,
233
- require=["iat", "iss", "exp", "sub"],
234
- algorithms=token_alg)
233
+ "verify_iat": True,
234
+ "verify_iss": False,
235
+ "verify_nbf": True,
236
+ "verify_signature": True
237
+ })
235
238
  if account_id and payload.get("sub") != account_id:
236
239
  if logger:
237
240
  logger.error(msg=f"Token does not belong to account '{account_id}'")
@@ -505,7 +508,7 @@ def jwt_refresh_tokens(account_id: str,
505
508
 
506
509
  def jwt_get_claims(token: str,
507
510
  errors: list[str] = None,
508
- logger: Logger = None) -> dict[str, Any] | None:
511
+ logger: Logger = None) -> dict[str, dict[str, Any]] | None:
509
512
  """
510
513
  Retrieve the claims set of a JWT *token*.
511
514
 
@@ -520,8 +523,6 @@ def jwt_get_claims(token: str,
520
523
  "kid": "A1234"
521
524
  },
522
525
  "payload": {
523
- "valid-from": <YYYY-MM-DDThh:mm:ss+00:00>
524
- "valid-until": <YYYY-MM-DDThh:mm:ss+00:00>
525
526
  "birthdate": "1980-01-01",
526
527
  "email": "jdoe@mail.com",
527
528
  "exp": 1516640454,
@@ -539,13 +540,13 @@ def jwt_get_claims(token: str,
539
540
  }
540
541
  }
541
542
 
542
- :param token: the token to be inspected for claims
543
+ :param token: the reference token
543
544
  :param errors: incidental error messages
544
545
  :param logger: optional logger
545
546
  :return: the token's claimset, or *None* if error
546
547
  """
547
548
  # initialize the return variable
548
- result: dict[str, Any] | None = None
549
+ result: dict[str, dict[str, Any]] | None = None
549
550
 
550
551
  if logger:
551
552
  logger.debug(msg="Retrieve claims for token")
@@ -562,7 +563,7 @@ def jwt_get_claims(token: str,
562
563
  exc_err: str = exc_format(exc=e,
563
564
  exc_info=sys.exc_info())
564
565
  if logger:
565
- logger.error(msg=f"Error retrieving the token's claimsn: {exc_err}")
566
+ logger.error(msg=f"Error retrieving the token's claims: {exc_err}")
566
567
  if isinstance(errors, list):
567
568
  errors.append(exc_err)
568
569
 
@@ -1,137 +0,0 @@
1
- import requests
2
- import sys
3
- from base64 import b64encode
4
- from datetime import datetime
5
- from logging import Logger
6
- from pypomes_core import TZ_LOCAL, exc_format
7
- from typing import Any
8
-
9
- # structure:
10
- # {
11
- # <provider-id>: {
12
- # "url": <strl>,
13
- # "user": <str>,
14
- # "pwd": <str>,
15
- # "basic-auth": <bool>,
16
- # "headers-data": <dict[str, str]>,
17
- # "body-data": <dict[str, str],
18
- # "token": <str>,
19
- # "expiration": <timestamp>
20
- # }
21
- # }
22
- _provider_registry: dict[str, dict[str, Any]] = {}
23
-
24
-
25
- def provider_register(provider_id: str,
26
- access_url: str,
27
- auth_user: str,
28
- auth_pwd: str,
29
- custom_auth: tuple[str, str] = None,
30
- headers_data: dict[str, str] = None,
31
- body_data: dict[str, str] = None) -> None:
32
- """
33
- Register an external authentication token provider.
34
-
35
- If specified, *custom_auth* provides key names for sending credentials (username and password, in this order)
36
- as key-value pairs in the body of the request. Otherwise, the external provider *provider_id* uses the standard
37
- HTTP Basic Authorization scheme, wherein the credentials are B64-encoded and send in the request headers.
38
-
39
- Optional constant key-value pairs (such as ['Content-Type', 'application/x-www-form-urlencoded']), to be
40
- added to the request headers, may be specified in *headers_data*. Likewise, optional constant key-value pairs
41
- (such as ['grant_type', 'client_credentials']), to be added to the request body, may be specified in *body_data*.
42
-
43
- :param provider_id: the provider's identification
44
- :param access_url: the url to request authentication tokens with
45
- :param auth_user: the basic authorization user
46
- :param auth_pwd: the basic authorization password
47
- :param custom_auth: optional key names for sending the credentials as key-value pairs in the body of the request
48
- :param headers_data: optional key-value pairs to be added to the request headers
49
- :param body_data: optional key-value pairs to be added to the request body
50
- """
51
- global _provider_registry # noqa: PLW0602
52
- _provider_registry[provider_id] = {
53
- "url": access_url,
54
- "user": auth_user,
55
- "pwd": auth_pwd,
56
- "custom-auth": custom_auth,
57
- "headers-data": headers_data,
58
- "body-data": body_data,
59
- "token": None,
60
- "expiration": datetime.now(tz=TZ_LOCAL).timestamp()
61
- }
62
-
63
-
64
- def provider_get_token(provider_id: str,
65
- errors: list[str] = None,
66
- logger: Logger = None) -> str | None:
67
- """
68
- Obtain an authentication token from the external provider *provider_id*.
69
-
70
- :param provider_id: the provider's identification
71
- :param errors: incidental error messages
72
- :param logger: optional logger
73
- """
74
- # initialize the return variable
75
- result: str | None = None
76
-
77
- global _provider_registry # noqa: PLW0602
78
- err_msg: str | None = None
79
- provider: dict[str, Any] = _provider_registry.get(provider_id)
80
- if provider:
81
- now: float = datetime.now(tz=TZ_LOCAL).timestamp()
82
- if now > provider.get("expiration"):
83
- user: str = provider.get("user")
84
- pwd: str = provider.get("pwd")
85
- headers_data: dict[str, str] = provider.get("headers-data") or {}
86
- body_data: dict[str, str] = provider.get("body-data") or {}
87
- custom_auth: tuple[str, str] = provider.get("custom-auth")
88
- if custom_auth:
89
- body_data[custom_auth[0]] = user
90
- body_data[custom_auth[1]] = pwd
91
- else:
92
- enc_bytes: bytes = b64encode(f"{user}:{pwd}".encode())
93
- headers_data["Authorization"] = f"Basic {enc_bytes.decode()}"
94
- url: str = provider.get("url")
95
- try:
96
- # typical return on a token request:
97
- # {
98
- # "token_type": "Bearer",
99
- # "access_token": <str>,
100
- # "expires_in": <number-of-seconds>,
101
- # optional data:
102
- # "refresh_token": <str>,
103
- # "refresh_expires_in": <number-of-seconds>
104
- # }
105
- response: requests.Response = requests.post(url=url,
106
- data=body_data,
107
- headers=headers_data,
108
- timeout=None)
109
- if response.status_code < 200 or response.status_code >= 300:
110
- # request resulted in error, report the problem
111
- err_msg = (f"POST '{url}': failed, "
112
- f"status {response.status_code}, reason '{response.reason}'")
113
- else:
114
- reply: dict[str, Any] = response.json()
115
- provider["token"] = reply.get("access_token")
116
- provider["expiration"] = now + int(reply.get("expires_in"))
117
- if logger:
118
- logger.debug(msg=f"POST '{url}': status {response.status_code}")
119
- except Exception as e:
120
- # the operation raised an exception
121
- err_msg = exc_format(exc=e,
122
- exc_info=sys.exc_info())
123
- err_msg = f"POST '{url}': error, '{err_msg}'"
124
- else:
125
- err_msg: str = f"Provider '{provider_id}' not registered"
126
-
127
- if err_msg:
128
- if isinstance(errors, list):
129
- errors.append(err_msg)
130
- if logger:
131
- logger.error(msg=err_msg)
132
- else:
133
- result = provider.get("token")
134
-
135
- return result
136
-
137
-
File without changes
File without changes
File without changes