pypomes-iam 0.6.0__py3-none-any.whl → 0.6.2__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_actions.py +8 -7
- pypomes_iam/iam_common.py +8 -7
- pypomes_iam/provider_pomes.py +125 -64
- {pypomes_iam-0.6.0.dist-info → pypomes_iam-0.6.2.dist-info}/METADATA +1 -2
- pypomes_iam-0.6.2.dist-info/RECORD +11 -0
- pypomes_iam-0.6.0.dist-info/RECORD +0 -11
- {pypomes_iam-0.6.0.dist-info → pypomes_iam-0.6.2.dist-info}/WHEEL +0 -0
- {pypomes_iam-0.6.0.dist-info → pypomes_iam-0.6.2.dist-info}/licenses/LICENSE +0 -0
pypomes_iam/iam_actions.py
CHANGED
|
@@ -59,7 +59,7 @@ def action_login(iam_server: IamServer,
|
|
|
59
59
|
errors=errors,
|
|
60
60
|
logger=logger)
|
|
61
61
|
if not errors:
|
|
62
|
-
user_data[UserParam.LOGIN_EXPIRATION] = int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout \
|
|
62
|
+
user_data[UserParam.LOGIN_EXPIRATION] = (int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout) \
|
|
63
63
|
if timeout else None
|
|
64
64
|
redirect_uri: str = args.get(UserParam.REDIRECT_URI)
|
|
65
65
|
user_data[UserParam.REDIRECT_URI] = redirect_uri
|
|
@@ -138,6 +138,7 @@ def action_token(iam_server: IamServer,
|
|
|
138
138
|
user_id=user_id,
|
|
139
139
|
errors=errors,
|
|
140
140
|
logger=logger)
|
|
141
|
+
# retrieve the stored access token
|
|
141
142
|
token: str = user_data[UserParam.ACCESS_TOKEN] if user_data else None
|
|
142
143
|
if token:
|
|
143
144
|
access_expiration: int = user_data.get(UserParam.ACCESS_EXPIRATION)
|
|
@@ -148,7 +149,7 @@ def action_token(iam_server: IamServer,
|
|
|
148
149
|
# access token has expired
|
|
149
150
|
refresh_token: str = user_data[UserParam.REFRESH_TOKEN]
|
|
150
151
|
if refresh_token:
|
|
151
|
-
refresh_expiration = user_data[UserParam.REFRESH_EXPIRATION]
|
|
152
|
+
refresh_expiration: int = user_data[UserParam.REFRESH_EXPIRATION]
|
|
152
153
|
if now < refresh_expiration:
|
|
153
154
|
header_data: dict[str, str] = {
|
|
154
155
|
"Content-Type": "application/json"
|
|
@@ -242,7 +243,7 @@ def action_callback(iam_server: IamServer,
|
|
|
242
243
|
users.pop(oauth_state)
|
|
243
244
|
code: str = args.get("code")
|
|
244
245
|
header_data: dict[str, str] = {
|
|
245
|
-
"Content-Type": "application/
|
|
246
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
246
247
|
}
|
|
247
248
|
body_data: dict[str, Any] = {
|
|
248
249
|
"grant_type": "authorization_code",
|
|
@@ -456,7 +457,7 @@ def __get_administrative_token(iam_server: IamServer,
|
|
|
456
457
|
logger=logger)
|
|
457
458
|
if registry and registry[IamParam.ADMIN_ID] and registry[IamParam.ADMIN_SECRET]:
|
|
458
459
|
header_data: dict[str, str] = {
|
|
459
|
-
"Content-Type": "application/
|
|
460
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
460
461
|
}
|
|
461
462
|
body_data: dict[str, str] = {
|
|
462
463
|
"grant-type": "password",
|
|
@@ -573,7 +574,7 @@ def __get_for_data(url: str,
|
|
|
573
574
|
logger.debug(msg=f"GET success, {json.dumps(obj=result,
|
|
574
575
|
ensure_ascii=False)}")
|
|
575
576
|
else:
|
|
576
|
-
# request
|
|
577
|
+
# request failed, report the problem
|
|
577
578
|
msg: str = f"GET failure, status {response.status_code}, reason {response.reason}"
|
|
578
579
|
if hasattr(response, "content") and response.content:
|
|
579
580
|
msg += f", content '{response.content}'"
|
|
@@ -615,7 +616,7 @@ def __post_data(url: str,
|
|
|
615
616
|
headers=header_data,
|
|
616
617
|
data=body_data)
|
|
617
618
|
if response.status_code >= 400:
|
|
618
|
-
# request
|
|
619
|
+
# request failed, report the problem
|
|
619
620
|
msg = f"POST failure, status {response.status_code}, reason {response.reason}"
|
|
620
621
|
if hasattr(response, "content") and response.content:
|
|
621
622
|
msg += f", content '{response.content}'"
|
|
@@ -735,7 +736,7 @@ def __post_for_token(iam_server: IamServer,
|
|
|
735
736
|
logger.debug(msg=f"POST success, {json.dumps(obj=result,
|
|
736
737
|
ensure_ascii=False)}")
|
|
737
738
|
else:
|
|
738
|
-
# request
|
|
739
|
+
# request failed, report the problem
|
|
739
740
|
err_msg = f"POST failure, status {response.status_code}, reason {response.reason}"
|
|
740
741
|
if hasattr(response, "content") and response.content:
|
|
741
742
|
err_msg += f", content '{response.content}'"
|
pypomes_iam/iam_common.py
CHANGED
|
@@ -58,16 +58,17 @@ class UserParam(StrEnum):
|
|
|
58
58
|
# Specifying configuration parameters with environment variables can be done in two ways:
|
|
59
59
|
#
|
|
60
60
|
# 1. for a single *IAM* server, specify the data set
|
|
61
|
-
# - *<APP_PREFIX>_IAM_ADMIN_ID* (optional, needed
|
|
62
|
-
# - *<APP_PREFIX>_IAM_ADMIN_PWD* (optional, needed
|
|
61
|
+
# - *<APP_PREFIX>_IAM_ADMIN_ID* (optional, needed if administrative duties are performed)
|
|
62
|
+
# - *<APP_PREFIX>_IAM_ADMIN_PWD* (optional, needed if administrative duties are performed)
|
|
63
63
|
# - *<APP_PREFIX>_IAM_CLIENT_ID* (required)
|
|
64
64
|
# - *<APP_PREFIX>_IAM_CLIENT_REALM* (required)
|
|
65
65
|
# - *<APP_PREFIX>_IAM_CLIENT_SECRET* (required)
|
|
66
|
-
# - *<APP_PREFIX>_IAM_ENDPOINT_CALLBACK* (
|
|
67
|
-
# - *<APP_PREFIX>
|
|
68
|
-
# - *<APP_PREFIX>
|
|
69
|
-
# - *<APP_PREFIX>
|
|
70
|
-
# - *<APP_PREFIX>
|
|
66
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_CALLBACK* (required)
|
|
67
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_EXCHANGE* (required)
|
|
68
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_LOGIN* (required)
|
|
69
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_LOGOUT* (required)
|
|
70
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_PROVIDER* (optional, needed if requesting tokens to providers)
|
|
71
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_TOKEN* (required)
|
|
71
72
|
# - *<APP_PREFIX>_IAM_LOGIN_TIMEOUT* (optional, defaults to no timeout)
|
|
72
73
|
# - *<APP_PREFIX>_IAM_PK_LIFETIME* (optional, defaults to non-terminating lifetime)
|
|
73
74
|
# - *<APP_PREFIX>_IAM_RECIPIENT_ATTR* (required)
|
pypomes_iam/provider_pomes.py
CHANGED
|
@@ -106,76 +106,137 @@ def provider_get_token(provider_id: str,
|
|
|
106
106
|
# initialize the return variable
|
|
107
107
|
result: str | None = None
|
|
108
108
|
|
|
109
|
-
err_msg: str | None = None
|
|
110
109
|
with _provider_lock:
|
|
111
110
|
provider: dict[str, Any] = _provider_registry.get(provider_id)
|
|
112
111
|
if provider:
|
|
113
|
-
now:
|
|
114
|
-
if now
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
body_data[custom_auth[0]] = user
|
|
122
|
-
body_data[custom_auth[1]] = pwd
|
|
123
|
-
else:
|
|
124
|
-
enc_bytes: bytes = b64encode(f"{user}:{pwd}".encode())
|
|
125
|
-
headers_data["Authorization"] = f"Basic {enc_bytes.decode()}"
|
|
112
|
+
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
113
|
+
if now < provider.get(ProviderParam.ACCESS_EXPIRATION):
|
|
114
|
+
# retrieve the stored access token
|
|
115
|
+
result = provider.get(ProviderParam.ACCESS_TOKEN)
|
|
116
|
+
else:
|
|
117
|
+
# access token has expired
|
|
118
|
+
header_data: dict[str, str] | None = None
|
|
119
|
+
body_data: dict[str, str] | None = None
|
|
126
120
|
url: str = provider.get(ProviderParam.URL)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
121
|
+
refresh_token: str = provider.get(ProviderParam.REFRESH_TOKEN)
|
|
122
|
+
if refresh_token:
|
|
123
|
+
# refresh token exists
|
|
124
|
+
refresh_expiration: int = provider.get(ProviderParam.REFRESH_EXPIRATION)
|
|
125
|
+
if now < refresh_expiration:
|
|
126
|
+
# refresh token has not expired
|
|
127
|
+
header_data: dict[str, str] = {
|
|
128
|
+
"Content-Type": "application/json"
|
|
129
|
+
}
|
|
130
|
+
body_data: dict[str, str] = {
|
|
131
|
+
"grant_type": "refresh_token",
|
|
132
|
+
"refresh_token": refresh_token
|
|
133
|
+
}
|
|
134
|
+
if not body_data:
|
|
135
|
+
# refresh token does not exist or has expired
|
|
136
|
+
user: str = provider.get(ProviderParam.USER)
|
|
137
|
+
pwd: str = provider.get(ProviderParam.PWD)
|
|
138
|
+
headers_data: dict[str, str] = provider.get(ProviderParam.HEADER_DATA) or {}
|
|
139
|
+
body_data: dict[str, str] = provider.get(ProviderParam.BODY_DATA) or {}
|
|
140
|
+
custom_auth: tuple[str, str] = provider.get(ProviderParam.CUSTOM_AUTH)
|
|
141
|
+
if custom_auth:
|
|
142
|
+
body_data[custom_auth[0]] = user
|
|
143
|
+
body_data[custom_auth[1]] = pwd
|
|
148
144
|
else:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
errors
|
|
174
|
-
|
|
175
|
-
logger.error(msg=err_msg)
|
|
176
|
-
else:
|
|
177
|
-
result = provider.get(ProviderParam.ACCESS_TOKEN)
|
|
145
|
+
enc_bytes: bytes = b64encode(f"{user}:{pwd}".encode())
|
|
146
|
+
headers_data["Authorization"] = f"Basic {enc_bytes.decode()}"
|
|
147
|
+
|
|
148
|
+
# obtain the token
|
|
149
|
+
token_data: dict[str, Any] = __post_for_token(url=url,
|
|
150
|
+
header_data=header_data,
|
|
151
|
+
body_data=body_data,
|
|
152
|
+
errors=errors,
|
|
153
|
+
logger=logger)
|
|
154
|
+
if token_data:
|
|
155
|
+
result = token_data.get("access_token")
|
|
156
|
+
provider[ProviderParam.ACCESS_TOKEN] = result
|
|
157
|
+
provider[ProviderParam.ACCESS_EXPIRATION] = now + token_data.get("expires_in")
|
|
158
|
+
refresh_token = token_data.get("refresh_token")
|
|
159
|
+
if refresh_token:
|
|
160
|
+
provider[ProviderParam.REFRESH_TOKEN] = refresh_token
|
|
161
|
+
refresh_exp: int = token_data.get("refresh_expires_in")
|
|
162
|
+
provider[ProviderParam.REFRESH_EXPIRATION] = (now + refresh_exp) \
|
|
163
|
+
if refresh_exp else sys.maxsize
|
|
164
|
+
|
|
165
|
+
elif logger or isinstance(errors, list):
|
|
166
|
+
msg: str = f"Unknown provider '{provider_id}'"
|
|
167
|
+
if logger:
|
|
168
|
+
logger.error(msg=msg)
|
|
169
|
+
if isinstance(errors, list):
|
|
170
|
+
errors.append(msg)
|
|
178
171
|
|
|
179
172
|
return result
|
|
180
173
|
|
|
181
174
|
|
|
175
|
+
def __post_for_token(url: str,
|
|
176
|
+
header_data: dict[str, str],
|
|
177
|
+
body_data: dict[str, Any],
|
|
178
|
+
errors: list[str] | None,
|
|
179
|
+
logger: Logger | None) -> dict[str, Any] | None:
|
|
180
|
+
"""
|
|
181
|
+
Send a *POST* request to *url* and return the token data obtained.
|
|
182
|
+
|
|
183
|
+
Token acquisition and token refresh are the two types of requests contemplated herein.
|
|
184
|
+
For the former, *header_data* and *body_data* will have contents customized to the specific provider,
|
|
185
|
+
whereas the latter's *body_data* will contain these two attributes:
|
|
186
|
+
- "grant_type": "refresh_token"
|
|
187
|
+
- "refresh_token": <current-refresh-token>
|
|
188
|
+
|
|
189
|
+
The typical data set returned contains the following attributes:
|
|
190
|
+
{
|
|
191
|
+
"token_type": "Bearer",
|
|
192
|
+
"access_token": <str>,
|
|
193
|
+
"expires_in": <number-of-seconds>,
|
|
194
|
+
"refresh_token": <str>,
|
|
195
|
+
"refesh_expires_in": <number-of-seconds>
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
:param url: the target URL
|
|
199
|
+
:param header_data: the data to send in the header of the request
|
|
200
|
+
:param body_data: the data to send in the body of the request
|
|
201
|
+
:param errors: incidental errors
|
|
202
|
+
:param logger: optional logger
|
|
203
|
+
:return: the token data, or *None* if error
|
|
204
|
+
"""
|
|
205
|
+
# initialize the return variable
|
|
206
|
+
result: dict[str, Any] | None = None
|
|
207
|
+
|
|
208
|
+
# log the POST
|
|
209
|
+
if logger:
|
|
210
|
+
logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
|
|
211
|
+
ensure_ascii=False)}")
|
|
212
|
+
try:
|
|
213
|
+
response: requests.Response = requests.post(url=url,
|
|
214
|
+
data=body_data,
|
|
215
|
+
headers=header_data,
|
|
216
|
+
timeout=None)
|
|
217
|
+
if response.status_code == 200:
|
|
218
|
+
# request succeeded
|
|
219
|
+
result = response.json()
|
|
220
|
+
if logger:
|
|
221
|
+
logger.debug(msg=f"POST success, status {response.status_code}")
|
|
222
|
+
else:
|
|
223
|
+
# request failed, report the problem
|
|
224
|
+
msg: str = (f"POST failure, "
|
|
225
|
+
f"status {response.status_code}, reason {response.reason}")
|
|
226
|
+
if hasattr(response, "content") and response.content:
|
|
227
|
+
msg += f", content '{response.content}'"
|
|
228
|
+
if logger:
|
|
229
|
+
logger.error(msg=msg)
|
|
230
|
+
if isinstance(errors, list):
|
|
231
|
+
errors.append(msg)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
# the operation raised an exception
|
|
234
|
+
err_msg = exc_format(exc=e,
|
|
235
|
+
exc_info=sys.exc_info())
|
|
236
|
+
msg: str = f"POST error, {err_msg}"
|
|
237
|
+
if logger:
|
|
238
|
+
logger.debug(msg=msg)
|
|
239
|
+
if isinstance(errors, list):
|
|
240
|
+
errors.append(msg)
|
|
241
|
+
|
|
242
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_iam
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
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
|
|
@@ -10,7 +10,6 @@ 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: cachetools>=6.2.1
|
|
14
13
|
Requires-Dist: flask>=3.1.2
|
|
15
14
|
Requires-Dist: pyjwt>=2.10.1
|
|
16
15
|
Requires-Dist: pypomes-core>=2.8.1
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pypomes_iam/__init__.py,sha256=_6tSFfjuU-5p6TAMqNLHSL6IQmaJMSYuEW-TG3ybhTI,1044
|
|
2
|
+
pypomes_iam/iam_actions.py,sha256=DhDZcY6j3uSWnm5Q5SrA5jriTUCatLqJKd9K7IjA2i8,39283
|
|
3
|
+
pypomes_iam/iam_common.py,sha256=ki_-m6fqJqUbGjgTD41r9zaE-FOXgA_c_tLisIYYTfU,15457
|
|
4
|
+
pypomes_iam/iam_pomes.py,sha256=XkxpwwGivUR3Y1TKR6McrqLUnpFJhRvIrsEn9T_Ut9A,7351
|
|
5
|
+
pypomes_iam/iam_services.py,sha256=IkCjrKDX1Ix7BiHh-BL3VKz5xogcNC8prXkHyJzQoZ8,15862
|
|
6
|
+
pypomes_iam/provider_pomes.py,sha256=3mMj5LQs53YEINUEOfFBAxOwOP3aOR_szlE4daEBLK0,10523
|
|
7
|
+
pypomes_iam/token_pomes.py,sha256=K4nSAotKUoHIE2s3ltc_nVimlNeKS9tnD-IlslkAvkk,6626
|
|
8
|
+
pypomes_iam-0.6.2.dist-info/METADATA,sha256=XeUm7yxjoWFURVvoHDSoYkF5iROCC696PPuHydJ1UIs,661
|
|
9
|
+
pypomes_iam-0.6.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
+
pypomes_iam-0.6.2.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
|
|
11
|
+
pypomes_iam-0.6.2.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
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=4sn9V4_fRXrljz41Dh7P6ng4aqFfp0pLnrq_rURDKt4,15363
|
|
4
|
-
pypomes_iam/iam_pomes.py,sha256=XkxpwwGivUR3Y1TKR6McrqLUnpFJhRvIrsEn9T_Ut9A,7351
|
|
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.6.0.dist-info/METADATA,sha256=xl-01mkMhab71h8WMqADHpX5qxS5ngFfcMyhSouQwt0,694
|
|
9
|
-
pypomes_iam-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
-
pypomes_iam-0.6.0.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
|
|
11
|
-
pypomes_iam-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|