pypomes-iam 0.5.7__py3-none-any.whl → 0.5.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.

Potentially problematic release.


This version of pypomes-iam might be problematic. Click here for more details.

pypomes_iam/iam_common.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import requests
2
2
  import sys
3
3
  from datetime import datetime
4
- from enum import StrEnum
4
+ from enum import StrEnum, auto
5
5
  from logging import Logger
6
6
  from pypomes_core import TZ_LOCAL, exc_format
7
7
  from pypomes_crypto import crypto_jwk_convert
@@ -13,42 +13,107 @@ class IamServer(StrEnum):
13
13
  """
14
14
  Supported IAM servers.
15
15
  """
16
- IAM_JUSRBR = "iam-jusbr",
17
- IAM_KEYCLOAK = "iam-keycloak"
16
+ JUSRBR = auto()
17
+ KEYCLOAK = auto()
18
18
 
19
19
 
20
+ class IamParam(StrEnum):
21
+ """
22
+ Parameters for configuring *IAM* servers.
23
+ """
24
+ ADMIN_ID = "admin-id"
25
+ ADMIN_SECRET = "admin-secret"
26
+ CLIENT_ID = "client-id"
27
+ CLIENT_REALM = "client-realm"
28
+ CLIENT_SECRET = "client-secret"
29
+ ENDPOINT_CALLBACK = "endpoint-callback"
30
+ ENDPOINT_LOGIN = "endpoint-login"
31
+ ENDPOINT_LOGOUT = "endpoint_logout"
32
+ ENDPOINT_TOKEN = "endpoint-token"
33
+ ENDPOINT_EXCHANGE = "endpoint-exchange"
34
+ LOGIN_TIMEOUT = "login-timeout"
35
+ PK_EXPIRATION = "pk-expiration"
36
+ PK_LIFETIME = "pk-lifetime"
37
+ PUBLIC_KEY = "public-key"
38
+ RECIPIENT_ATTR = "recipient-attr"
39
+ URL_BASE = "url-base"
40
+ USERS = "users"
41
+
42
+
43
+ class UserParam(StrEnum):
44
+ """
45
+ Parameters for handling *IAM* users.
46
+ """
47
+ ACCESS_TOKEN = "access-token"
48
+ REFRESH_TOKEN = "refresh-token"
49
+ ACCESS_EXPIRATION = "access-expiration"
50
+ REFRESH_EXPIRATION = "refresh-expiration"
51
+ # transient attributes
52
+ LOGIN_EXPIRATION = "login-expiration"
53
+ LOGIN_ID = "login-id"
54
+ REDIRECT_URI = "redirect-uri"
55
+
56
+
57
+ # The configuration parameters for the IAM servers are specified dynamically dynamically with *iam_setup()*
58
+ # Specifying configuration parameters with environment variables can be done in two ways:
59
+ #
60
+ # 1. for a single *IAM* server, specify the data set
61
+ # - *<APP_PREFIX>_IAM_SERVER* (required, one of *jusbr*, *keycloak*)
62
+ # - *<APP_PREFIX>_IAM_ADMIN_ID* (optional, needed only if administrative duties are performed)
63
+ # - *<APP_PREFIX>_IAM_ADMIN_PWD* (optional, needed only if administrative duties are performed)
64
+ # - *<APP_PREFIX>_IAM_CLIENT_ID* (required)
65
+ # - *<APP_PREFIX>_IAM_CLIENT_REALM* (required)
66
+ # - *<APP_PREFIX>_IAM_CLIENT_SECRET* (required)
67
+ # - *<APP_PREFIX>_IAM_ENDPOINT_CALLBACK* (optional)
68
+ # - *<APP_PREFIX>_IAM_ENDPOINT_LOGIN* (optional)
69
+ # - *<APP_PREFIX>_IAM_ENDPOINT_LOGOUT* (optional)
70
+ # - *<APP_PREFIX>_IAM_ENDPOINT_TOKEN* (optional)
71
+ # - *<APP_PREFIX>_IAM_ENDPOINT_EXCHANGE* (optional)
72
+ # - *<APP_PREFIX>_IAM_LOGIN_TIMEOUT* (optional, defaults to no timeout)
73
+ # - *<APP_PREFIX>_IAM_PK_LIFETIME* (optional, defaults to non-terminating lifetime)
74
+ # - *<APP_PREFIX>_IAM_RECIPIENT_ATTR* (required)
75
+ # - *<APP_PREFIX>_IAM_URL_BASE* (required)
76
+ #
77
+ # 2. for multiple *IAM* servers, specify the data set above for each server,
78
+ # respectively replacing *IAM* with a name in *IamServer* (currently, *JUSBR* and *KEYCLOAK* are supported).
79
+ #
80
+ # 3. the parameters *PUBLIC_KEY*, *PK_EXPIRATION*, and *USERS* cannot be assigned values,
81
+ # as they are reserved for internal use
82
+
20
83
  # registry structure:
21
84
  # { <IamServer>:
22
85
  # {
23
86
  # "base-url": <str>,
87
+ # "admin-id": <str>,
88
+ # "admin-secret": <str>,
24
89
  # "client-id": <str>,
25
90
  # "client-secret": <str>,
91
+ # "client-realm": <str,
26
92
  # "client-timeout": <int>,
27
93
  # "recipient-attr": <str>,
28
94
  # "public-key": <str>,
29
95
  # "pk-lifetime": <int>,
30
96
  # "pk-expiration": <int>,
31
- # "cache": <FIFOCache>
97
+ # "users": {}
32
98
  # },
33
99
  # ...
34
100
  # }
35
- # data in "cache":
101
+ # data in "users":
36
102
  # {
37
- # "users": {
38
- # "<user-id>": {
39
- # "access-token": <str>
40
- # "refresh-token": <str>
41
- # "access-expiration": <timestamp>,
42
- # "refresh-expiration": <timestamp>,
43
- # # transient attributes:
44
- # "login-expiration": <timestamp>,
45
- # "login-id": <str>,
46
- # "redirect-uri": <str>
47
- # }
48
- # },
103
+ # "<user-id>": {
104
+ # "access-token": <str>
105
+ # "refresh-token": <str>
106
+ # "access-expiration": <timestamp>,
107
+ # "refresh-expiration": <timestamp>,
108
+ # # transient attributes
109
+ # "login-expiration": <timestamp>,
110
+ # "login-id": <str>,
111
+ # "redirect-uri": <str>
112
+ # },
49
113
  # ...
50
114
  # }
51
- _IAM_SERVERS: Final[dict[IamServer, dict[str, Any]]] = {}
115
+ _IAM_SERVERS: Final[dict[IamServer, dict[IamParam, Any]]] = {}
116
+
52
117
 
53
118
  # the lock protecting the data in '_IAM_SERVERS'
54
119
  # (because it is 'Final' and set at declaration time, it can be accessed through simple imports)
@@ -66,15 +131,15 @@ def _iam_server_from_endpoint(endpoint: str,
66
131
  :param logger: optional logger
67
132
  :return: the corresponding *IAM* server, or *None* if one could not be obtained
68
133
  """
69
- # declare the return variable
70
- result: IamServer | None
71
-
72
- if endpoint.startswith("jusbr"):
73
- result = IamServer.IAM_JUSRBR
74
- elif endpoint.startswith("keycloak"):
75
- result = IamServer.IAM_KEYCLOAK
76
- else:
77
- result = None
134
+ # initialize the return variable
135
+ result: IamServer | None = None
136
+
137
+ for iam_server in _IAM_SERVERS:
138
+ if endpoint.startswith(iam_server):
139
+ result = iam_server
140
+ break
141
+
142
+ if not result:
78
143
  msg: str = f"Unable to find a IAM server to service endpoint '{endpoint}'"
79
144
  if logger:
80
145
  logger.error(msg=msg)
@@ -98,8 +163,9 @@ def _iam_server_from_issuer(issuer: str,
98
163
  # initialize the return variable
99
164
  result: IamServer | None = None
100
165
 
101
- for iam_server, server_data in _IAM_SERVERS.items():
102
- if server_data["base-url"] == issuer:
166
+ for iam_server, registry in _IAM_SERVERS.items():
167
+ base_url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
168
+ if base_url == issuer:
103
169
  result = IamServer(iam_server)
104
170
  break
105
171
 
@@ -161,9 +227,10 @@ def _get_public_key(iam_server: IamServer,
161
227
  logger=logger)
162
228
  if registry:
163
229
  now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
164
- if now > registry["pk-expiration"]:
230
+ if now > registry[IamParam.PK_EXPIRATION]:
165
231
  # obtain the JWKS (JSON Web Key Set) from the token issuer
166
- url: str = f"{registry["base-url"]}/protocol/openid-connect/certs"
232
+ base_url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
233
+ url: str = f"{base_url}/protocol/openid-connect/certs"
167
234
  if logger:
168
235
  logger.debug(msg=f"Obtaining signature public key used by IAM server '{iam_server}'")
169
236
  logger.debug(msg=f"GET {url}")
@@ -174,9 +241,8 @@ def _get_public_key(iam_server: IamServer,
174
241
  if logger:
175
242
  logger.debug(msg=f"GET success, status {response.status_code}")
176
243
  # select the appropriate JWK
177
- reply: dict[str, Any] = response.json()
244
+ reply: dict[str, list[dict[str, str]]] = response.json()
178
245
  jwk: dict[str, str] | None = None
179
- # replay["keys"]: list[dict[str, str]]
180
246
  for key in reply["keys"]:
181
247
  if key.get("use") == "sig":
182
248
  jwk = key
@@ -185,11 +251,11 @@ def _get_public_key(iam_server: IamServer,
185
251
  # convert from 'JWK' to 'PEM' and save it for further use
186
252
  result = crypto_jwk_convert(jwk=jwk,
187
253
  fmt="PEM")
188
- registry["public-key"] = result
189
- lifetime: int = registry["pk-lifetime"] or 0
190
- registry["pk-expiration"] = now + lifetime if lifetime else sys.maxsize
254
+ registry[IamParam.PUBLIC_KEY] = result
255
+ lifetime: int = registry[IamParam.PK_LIFETIME] or 0
256
+ registry[IamParam.PK_EXPIRATION] = now + lifetime if lifetime else sys.maxsize
191
257
  if logger:
192
- logger.debug(f"Public key obtained and saved")
258
+ logger.debug("Public key obtained and saved")
193
259
  else:
194
260
  msg = "Signature public key missing from the token issuer's JWKS"
195
261
  if logger:
@@ -212,7 +278,7 @@ def _get_public_key(iam_server: IamServer,
212
278
  if isinstance(errors, list):
213
279
  errors.append(msg)
214
280
  else:
215
- result = registry["public-key"]
281
+ result = registry[IamParam.PUBLIC_KEY]
216
282
 
217
283
  return result
218
284
 
@@ -267,10 +333,10 @@ def _get_user_data(iam_server: IamServer,
267
333
  result = users.get(user_id)
268
334
  if not result:
269
335
  result = {
270
- "access-token": None,
271
- "refresh-token": None,
272
- "access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
273
- "refresh-expiration": sys.maxsize
336
+ UserParam.ACCESS_TOKEN: None,
337
+ UserParam.REFRESH_TOKEN: None,
338
+ UserParam.ACCESS_EXPIRATION: int(datetime.now(tz=TZ_LOCAL).timestamp()),
339
+ UserParam.REFRESH_EXPIRATION: sys.maxsize
274
340
  }
275
341
  users[user_id] = result
276
342
  if logger:
@@ -296,10 +362,10 @@ def _get_iam_registry(iam_server: IamServer,
296
362
  result: dict[str, Any] | None
297
363
 
298
364
  match iam_server:
299
- case IamServer.IAM_JUSRBR:
300
- result = _IAM_SERVERS[IamServer.IAM_JUSRBR]
301
- case IamServer.IAM_KEYCLOAK:
302
- result = _IAM_SERVERS[IamServer.IAM_KEYCLOAK]
365
+ case IamServer.JUSRBR:
366
+ result = _IAM_SERVERS[IamServer.JUSRBR]
367
+ case IamServer.KEYCLOAK:
368
+ result = _IAM_SERVERS[IamServer.KEYCLOAK]
303
369
  case _:
304
370
  result = None
305
371
  msg = f"Unknown IAM server '{iam_server}'"
@@ -315,14 +381,14 @@ def _get_iam_users(iam_server: IamServer,
315
381
  errors: list[str] | None,
316
382
  logger: Logger | None) -> dict[str, dict[str, Any]]:
317
383
  """
318
- Retrieve the cache storage in *iam_server*'s registry.
384
+ Retrieve the users data storage in *iam_server*'s registry.
319
385
 
320
386
  :param iam_server: the reference registered *IAM* server
321
387
  :param errors: incidental error messages
322
388
  :param logger: optional logger
323
- :return: the cache storage in *iam_server*'s registry, or *None* if the server is unknown
389
+ :return: the users data storage in *iam_server*'s registry, or *None* if the server is unknown
324
390
  """
325
391
  registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
326
392
  errors=errors,
327
393
  logger=logger)
328
- return registry["cache"]["users"] if registry else None
394
+ return registry[IamParam.USERS] if registry else None
@@ -0,0 +1,156 @@
1
+ from flask import Flask
2
+ from logging import Logger
3
+ from pypomes_core import APP_PREFIX, env_get_int, env_get_str
4
+ from typing import Any
5
+
6
+ from .iam_common import (
7
+ _IAM_SERVERS, IamServer, IamParam, _iam_lock
8
+ )
9
+ from .iam_actions import action_token
10
+ from .iam_services import (
11
+ service_login, service_logout, service_callback, service_exchange, service_token
12
+ )
13
+
14
+
15
+ def iam_setup(flask_app: Flask,
16
+ iam_server: IamServer,
17
+ base_url: str,
18
+ client_id: str,
19
+ client_realm: str,
20
+ client_secret: str | None,
21
+ recipient_attribute: str,
22
+ admin_id: str = None,
23
+ admin_secret: str = None,
24
+ login_timeout: int = None,
25
+ public_key_lifetime: int = None,
26
+ callback_endpoint: str = None,
27
+ exchange_endpoint: str = None,
28
+ login_endpoint: str = None,
29
+ logout_endpoint: str = None,
30
+ token_endpoint: str = None) -> None:
31
+ """
32
+ Establish the provided parameters for configuring the *IAM* server *iam_server*.
33
+
34
+ The parameters *admin_id* and *admin_* are required only if administrative are task are planned.
35
+ The optional parameter *client_timeout* refers to the maximum time in seconds allowed for the
36
+ user to login at the *IAM* server's login page, and defaults to no time limit.
37
+
38
+ The parameter *client_secret* is required in most requests to the *IAM* server. In the case
39
+ it is not provided, but *admin_id* and *admin_secret* are, it is obtained from the *IAM* server itself
40
+ the first time it is needed.
41
+
42
+ :param flask_app: the Flask application
43
+ :param iam_server: identifies the supported *IAM* server (*jusbr* or *keycloak*)
44
+ :param base_url: base URL to request services
45
+ :param client_id: the client's identification with the *IAM* server
46
+ :param client_realm: the client realm
47
+ :param client_secret: the client's password with the *IAM* server
48
+ :param recipient_attribute: attribute in the token's payload holding the token's subject
49
+ :param admin_id: identifies the realm administrator
50
+ :param admin_secret: password for the realm administrator
51
+ :param login_timeout: timeout for login authentication (in seconds,defaults to no timeout)
52
+ :param public_key_lifetime: how long to use *IAM* server's public key, before refreshing it (in seconds)
53
+ :param callback_endpoint: endpoint for the callback from the front end
54
+ :param exchange_endpoint: endpoint for requesting token exchange
55
+ :param login_endpoint: endpoint for redirecting user to the *IAM* server's login page
56
+ :param logout_endpoint: endpoint for terminating user access
57
+ :param token_endpoint: endpoint for retrieving authentication token
58
+ """
59
+
60
+ # configure the Keycloak registry
61
+ with _iam_lock:
62
+ _IAM_SERVERS[iam_server] = {
63
+ IamParam.URL_BASE: base_url,
64
+ IamParam.CLIENT_ID: client_id,
65
+ IamParam.CLIENT_REALM: client_realm,
66
+ IamParam.CLIENT_SECRET: client_secret,
67
+ IamParam.RECIPIENT_ATTR: recipient_attribute,
68
+ IamParam.ADMIN_ID: admin_id,
69
+ IamParam.ADMIN_SECRET: admin_secret,
70
+ IamParam.LOGIN_TIMEOUT: login_timeout,
71
+ IamParam.PK_LIFETIME: public_key_lifetime,
72
+ IamParam.PK_EXPIRATION: 0,
73
+ IamParam.PUBLIC_KEY: None,
74
+ IamParam.USERS: {}
75
+ }
76
+
77
+ # establish the endpoints
78
+ if callback_endpoint:
79
+ flask_app.add_url_rule(rule=callback_endpoint,
80
+ endpoint=f"{iam_server}-callback",
81
+ view_func=service_callback,
82
+ methods=["GET"])
83
+ if login_endpoint:
84
+ flask_app.add_url_rule(rule=login_endpoint,
85
+ endpoint=f"{iam_server}-login",
86
+ view_func=service_login,
87
+ methods=["GET"])
88
+ if logout_endpoint:
89
+ flask_app.add_url_rule(rule=logout_endpoint,
90
+ endpoint=f"{iam_server}-logout",
91
+ view_func=service_logout,
92
+ methods=["GET"])
93
+ if token_endpoint:
94
+ flask_app.add_url_rule(rule=token_endpoint,
95
+ endpoint=f"{iam_server}-token",
96
+ view_func=service_token,
97
+ methods=["GET"])
98
+ if exchange_endpoint:
99
+ flask_app.add_url_rule(rule=exchange_endpoint,
100
+ endpoint=f"{iam_server}-exchange",
101
+ view_func=service_exchange,
102
+ methods=["POST"])
103
+
104
+
105
+ def iam_get_env_parameters(iam_prefix: str = None) -> dict[str, Any]:
106
+ """
107
+ Retrieve the set parameters for a *IAM* server from the environment.
108
+
109
+ the parameters are returned ready to be used as a '**kwargs' parameter set in a call to *iam_setup()*,
110
+ and sorted in the order appropriate to use them instead with a '*args' parameter set.
111
+
112
+ :param iam_prefix: the prefix classifying the parameters
113
+ :return: the sorted parameters classified by *prefix*
114
+ """
115
+ return {
116
+ "url_base": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_URL_BASE"),
117
+ "client_id": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_CLIENT_ID"),
118
+ "client_realm": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_CLIENT_REALM"),
119
+ "client_secret": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_CLIENT_SECRET"),
120
+ "recipient_attr": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_RECIPIENT_ATTR"),
121
+ "admin_id": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ADMIN_ID"),
122
+ "admin_secret": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ADMIN_SECRET"),
123
+ "login_timeout": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_LOGIN_TIMEOUT"),
124
+ "public_key_lifetime": env_get_int(key=f"{APP_PREFIX}_{iam_prefix}_PUBLIC_KEY_LIFETIME"),
125
+ "callback_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ENDPOINT_CALLBACK"),
126
+ "exchange_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ENDPOINT_EXCHANGE"),
127
+ "login_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ENDPOINT_LOGIN"),
128
+ "logout_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}__ENDPOINT_LOGOUT"),
129
+ "token_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ENDPOINT_TOKEN")
130
+ }
131
+
132
+
133
+ def iam_get_token(iam_server: IamServer,
134
+ user_id: str,
135
+ errors: list[str] = None,
136
+ logger: Logger = None) -> str:
137
+ """
138
+ Retrieve an authentication token for *user_id*.
139
+
140
+ :param iam_server: identifies the *IAM* server
141
+ :param user_id: identifies the user
142
+ :param errors: incidental errors
143
+ :param logger: optional logger
144
+ :return: the uthentication tokem
145
+ """
146
+ # declare the return variable
147
+ result: str
148
+
149
+ # retrieve the token
150
+ args: dict[str, Any] = {"user-id": user_id}
151
+ with _iam_lock:
152
+ result = action_token(iam_server=iam_server,
153
+ args=args,
154
+ errors=errors,
155
+ logger=logger)
156
+ return result
@@ -4,7 +4,7 @@ from logging import Logger
4
4
  from typing import Any
5
5
 
6
6
  from .iam_common import (
7
- IamServer, _iam_lock,
7
+ IamServer, IamParam, _iam_lock,
8
8
  _get_iam_registry, _get_public_key,
9
9
  _iam_server_from_endpoint, _iam_server_from_issuer
10
10
  )
@@ -73,7 +73,7 @@ def __request_validate(request: Request) -> Response:
73
73
  errors=None,
74
74
  logger=__IAM_LOGGER)
75
75
  if registry:
76
- recipient_attr = registry["recipient-attr"]
76
+ recipient_attr = registry[IamParam.RECIPIENT_ATTR]
77
77
  public_key: str = _get_public_key(iam_server=iam_server,
78
78
  errors=None,
79
79
  logger=__IAM_LOGGER)
@@ -90,7 +90,7 @@ def __request_validate(request: Request) -> Response:
90
90
  elif __IAM_LOGGER:
91
91
  __IAM_LOGGER.error("; ".join(errors))
92
92
  if bad_token and __IAM_LOGGER:
93
- __IAM_LOGGER.error(f"authorization refused for token {token}")
93
+ __IAM_LOGGER.error(f"Authorization refused for token {token}")
94
94
 
95
95
  # deny the authorization
96
96
  if bad_token:
@@ -256,9 +256,9 @@ def service_callback() -> Response:
256
256
  else:
257
257
  result = jsonify({"user-id": token_data[0],
258
258
  "access-token": token_data[1]})
259
- # log the response
260
259
  if __IAM_LOGGER:
261
- __IAM_LOGGER.debug(msg=f"Response {result}, {result.get_data(as_text=True)}")
260
+ # log the response (the returned data is not logged, as it contains the token)
261
+ __IAM_LOGGER.debug(msg=f"Response {result}")
262
262
 
263
263
  return result
264
264
 
@@ -317,9 +317,9 @@ def service_token() -> Response:
317
317
  else:
318
318
  result = jsonify({"user-id": user_id,
319
319
  "access-token": token})
320
- # log the response
321
320
  if __IAM_LOGGER:
322
- __IAM_LOGGER.debug(msg=f"Response {result}, {result.get_data(as_text=True)}")
321
+ # log the response (the returned data is not logged, as it contains the token)
322
+ __IAM_LOGGER.debug(msg=f"Response {result}")
323
323
 
324
324
  return result
325
325
 
@@ -3,22 +3,42 @@ import requests
3
3
  import sys
4
4
  from base64 import b64encode
5
5
  from datetime import datetime
6
+ from enum import StrEnum
6
7
  from logging import Logger
7
8
  from pypomes_core import TZ_LOCAL, exc_format
8
9
  from threading import Lock
9
10
  from typing import Any, Final
10
11
 
12
+
13
+ class ProviderParam(StrEnum):
14
+ """
15
+ Parameters for configuring a *JWT* token provider.
16
+ """
17
+ URL = "url"
18
+ USER = "user"
19
+ PWD = "pwd"
20
+ CUSTOM_AUTH = "custom-auth"
21
+ HEADER_DATA = "headers-data"
22
+ BODY_DATA = "body-data"
23
+ ACCESS_TOKEN = "access-token"
24
+ ACCESS_EXPIRATION = "access-expiration"
25
+ REFRESH_TOKEN = "refresh-token"
26
+ REFRESH_EXPIRATION = "refresh-expiration"
27
+
28
+
11
29
  # structure:
12
30
  # {
13
31
  # <provider-id>: {
14
32
  # "url": <strl>,
15
33
  # "user": <str>,
16
34
  # "pwd": <str>,
17
- # "basic-auth": <bool>,
35
+ # "custom-auth": <bool>,
18
36
  # "headers-data": <dict[str, str]>,
19
37
  # "body-data": <dict[str, str],
20
38
  # "access-token": <str>,
21
- # "access-expiration": <timestamp>
39
+ # "access-expiration": <timestamp>,
40
+ # "refresh-token": <str>,
41
+ # "refresh-expiration": <timestamp>
22
42
  # }
23
43
  # }
24
44
  _provider_registry: Final[dict[str, dict[str, Any]]] = {}
@@ -58,16 +78,16 @@ def provider_register(provider_id: str,
58
78
 
59
79
  with _provider_lock:
60
80
  _provider_registry[provider_id] = {
61
- "url": auth_url,
62
- "user": auth_user,
63
- "pwd": auth_pwd,
64
- "custom-auth": custom_auth,
65
- "headers-data": headers_data,
66
- "body-data": body_data,
67
- "access-token": None,
68
- "access-expiration": 0,
69
- "refresh-token": None,
70
- "refresh-expiration": 0
81
+ ProviderParam.URL: auth_url,
82
+ ProviderParam.USER: auth_user,
83
+ ProviderParam.PWD: auth_pwd,
84
+ ProviderParam.CUSTOM_AUTH: custom_auth,
85
+ ProviderParam.HEADER_DATA: headers_data,
86
+ ProviderParam.BODY_DATA: body_data,
87
+ ProviderParam.ACCESS_TOKEN: None,
88
+ ProviderParam.ACCESS_EXPIRATION: 0,
89
+ ProviderParam.REFRESH_TOKEN: None,
90
+ ProviderParam.REFRESH_EXPIRATION: 0
71
91
  }
72
92
 
73
93
 
@@ -91,19 +111,19 @@ def provider_get_token(provider_id: str,
91
111
  provider: dict[str, Any] = _provider_registry.get(provider_id)
92
112
  if provider:
93
113
  now: float = datetime.now(tz=TZ_LOCAL).timestamp()
94
- if now > provider.get("access-expiration"):
95
- user: str = provider.get("user")
96
- pwd: str = provider.get("pwd")
97
- headers_data: dict[str, str] = provider.get("headers-data") or {}
98
- body_data: dict[str, str] = provider.get("body-data") or {}
99
- custom_auth: tuple[str, str] = provider.get("custom-auth")
114
+ if now > provider.get(ProviderParam.ACCESS_EXPIRATION):
115
+ user: str = provider.get(ProviderParam.USER)
116
+ pwd: str = provider.get(ProviderParam.PWD)
117
+ headers_data: dict[str, str] = provider.get(ProviderParam.HEADER_DATA) or {}
118
+ body_data: dict[str, str] = provider.get(ProviderParam.BODY_DATA) or {}
119
+ custom_auth: tuple[str, str] = provider.get(ProviderParam.CUSTOM_AUTH)
100
120
  if custom_auth:
101
121
  body_data[custom_auth[0]] = user
102
122
  body_data[custom_auth[1]] = pwd
103
123
  else:
104
124
  enc_bytes: bytes = b64encode(f"{user}:{pwd}".encode())
105
125
  headers_data["Authorization"] = f"Basic {enc_bytes.decode()}"
106
- url: str = provider.get("url")
126
+ url: str = provider.get(ProviderParam.URL)
107
127
  if logger:
108
128
  logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
109
129
  ensure_ascii=False)}")
@@ -130,14 +150,14 @@ def provider_get_token(provider_id: str,
130
150
  if logger:
131
151
  logger.debug(msg=f"POST success, status {response.status_code}")
132
152
  reply: dict[str, Any] = response.json()
133
- provider["access-token"] = reply.get("access_token")
134
- provider["access-expiration"] = now + int(reply.get("expires_in"))
135
- if reply.get("refresh_token"):
136
- provider["refresh-token"] = reply["refresh_token"]
153
+ provider[ProviderParam.ACCESS_TOKEN] = reply.get("access_token")
154
+ provider[ProviderParam.ACCESS_EXPIRATION] = now + int(reply.get("expires_in"))
155
+ if reply.get(ProviderParam.REFRESH_TOKEN):
156
+ provider[ProviderParam.REFRESH_TOKEN] = reply["refresh_token"]
137
157
  if reply.get("refresh_expires_in"):
138
- provider["refresh-expiration"] = now + int(reply.get("refresh_expires_in"))
158
+ provider[ProviderParam.REFRESH_EXPIRATION] = now + int(reply.get("refresh_expires_in"))
139
159
  else:
140
- provider["refresh-expiration"] = sys.maxsize
160
+ provider[ProviderParam.REFRESH_EXPIRATION] = sys.maxsize
141
161
  if logger:
142
162
  logger.debug(msg=f"POST {url}: status {response.status_code}")
143
163
  except Exception as e:
@@ -154,7 +174,7 @@ def provider_get_token(provider_id: str,
154
174
  if logger:
155
175
  logger.error(msg=err_msg)
156
176
  else:
157
- result = provider.get("access-token")
177
+ result = provider.get(ProviderParam.ACCESS_TOKEN)
158
178
 
159
179
  return result
160
180
 
@@ -117,8 +117,6 @@ def token_validate(token: str,
117
117
  "verify_nbf": False,
118
118
  "verify_signature": token_alg in ["RS256", "RS512"] and public_key is not None
119
119
  }
120
- if issuer:
121
- options["require"].append("iss")
122
120
  try:
123
121
  # raises:
124
122
  # InvalidTokenError: token is invalid
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_iam
3
- Version: 0.5.7
3
+ Version: 0.5.9
4
4
  Summary: A collection of Python pomes, penyeach (IAM modules)
5
5
  Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-IAM
6
6
  Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-IAM/issues
@@ -0,0 +1,11 @@
1
+ pypomes_iam/__init__.py,sha256=_6tSFfjuU-5p6TAMqNLHSL6IQmaJMSYuEW-TG3ybhTI,1044
2
+ pypomes_iam/iam_actions.py,sha256=Bmd8rBg3948Afsg10B6B1ZrFY4wYtbxi55rX4Rlqiyk,39167
3
+ pypomes_iam/iam_common.py,sha256=asgool1T1ja1RKtQP1h71EeG3SJf8UX59NEDisHqLb8,15672
4
+ pypomes_iam/iam_pomes.py,sha256=fSB4KnCVM9HKZ6_LBfud9vtMM4psNQPrP98QDal3l9Y,7342
5
+ pypomes_iam/iam_services.py,sha256=IkCjrKDX1Ix7BiHh-BL3VKz5xogcNC8prXkHyJzQoZ8,15862
6
+ pypomes_iam/provider_pomes.py,sha256=N0nL9_hgHmAjG9JKFoXC33zk8b1ckPG1veu1jTp-2JE,8045
7
+ pypomes_iam/token_pomes.py,sha256=K4nSAotKUoHIE2s3ltc_nVimlNeKS9tnD-IlslkAvkk,6626
8
+ pypomes_iam-0.5.9.dist-info/METADATA,sha256=iTKxygsO09k_vx4DEmbTc7IIkwt1_8Fu9z-YpybSM24,694
9
+ pypomes_iam-0.5.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ pypomes_iam-0.5.9.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
11
+ pypomes_iam-0.5.9.dist-info/RECORD,,