pypomes-iam 0.2.9__py3-none-any.whl → 0.7.0__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_pomes.py CHANGED
@@ -1,213 +1,156 @@
1
- import json
2
- import requests
3
- from flask import Response, request, jsonify
1
+ from flask import Flask
4
2
  from logging import Logger
3
+ from pypomes_core import APP_PREFIX, env_get_int, env_get_str
5
4
  from typing import Any
6
5
 
7
6
  from .iam_common import (
8
- IAM_SERVERS, IamServer,
9
- _service_login, _service_logout,
10
- _service_callback, _service_token, _log_init
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
11
12
  )
12
13
 
13
14
 
14
- # @flask_app.route(rule=<login_endpoint>, # JUSBR_LOGIN_ENDPOINT: /iam/jusbr:login
15
- # methods=["GET"])
16
- # @flask_app.route(rule=<login_endpoint>, # KEYCLOAK_LOGIN_ENDPOINT: /iam/keycloak:logout
17
- # methods=["GET"])
18
- def service_login() -> Response:
19
- """
20
- Entry point for the JusBR login service.
21
-
22
- Redirect the request to the JusBR authentication page, with the appropriate parameters.
23
-
24
- :return: the response from the redirect operation
25
- """
26
- # retrieve logger and registry
27
- registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
28
- logger: Logger = registry["logger"]
29
-
30
- # log the request
31
- if logger:
32
- logger.debug(msg=_log_init(request=request))
33
-
34
- # obtain the login URL
35
- login_data: dict[str, str] = _service_login(registry=registry,
36
- args=request.args,
37
- logger=logger)
38
- result = jsonify(login_data)
39
-
40
- # log the response
41
- if logger:
42
- logger.debug(msg=f"Response {result}")
43
-
44
- return result
45
-
46
-
47
- # @flask_app.route(rule=<logout_endpoint>, # JUSBR_LOGOUT_ENDPOINT: /iam/jusbr:logout
48
- # methods=["GET"])
49
- # @flask_app.route(rule=<login_endpoint>, # KEYCLOAK_LOGOUT_ENDPOINT: /iam/keycloak:logout
50
- # methods=["GET"])
51
- def service_logout() -> Response:
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:
52
31
  """
53
- Entry point for the JusBR logout service.
54
-
55
- Remove all data associating the user with JusBR from the registry.
56
-
57
- :return: response *OK*
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 (currently, *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
58
  """
59
- # retrieve logger and registry
60
- registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
61
- logger: Logger = registry["logger"]
62
-
63
- # log the request
64
- if logger:
65
- logger.debug(msg=_log_init(request=request))
66
-
67
- # logout the user
68
- _service_logout(registry=registry,
69
- args=request.args,
70
- logger=logger)
71
-
72
- result: Response = Response(status=200)
73
-
74
- # log the response
75
- if logger:
76
- logger.debug(msg=f"Response {result}")
77
-
78
- return result
79
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
+ }
80
76
 
81
- # @flask_app.route(rule=<callback_endpoint>, # JUSBR_CALLBACK_ENDPOINT: /iam/jusbr:callback
82
- # methods=["GET", "POST"])
83
- # @flask_app.route(rule=<callback_endpoint>, # KEYCLOAK_CALLBACK_ENDPOINT: /iam/keycloak:callback
84
- # methods=["POST"])
85
- def service_callback() -> Response:
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]:
86
106
  """
87
- Entry point for the callback from JusBR on authentication operation.
107
+ Retrieve the set parameters for a *IAM* server from the environment.
88
108
 
89
- This callback is typically invoked from a front-end application after a successful login at the
90
- JusBR login page, forwarding the data received.
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.
91
111
 
92
- :return: the response containing the token, or *BAD REQUEST*
112
+ :param iam_prefix: the prefix classifying the parameters
113
+ :return: the sorted parameters classified by *prefix*
93
114
  """
94
- # retrieve logger and registry
95
- registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
96
- logger: Logger = registry["logger"]
97
-
98
- # log the request
99
- if logger:
100
- logger.debug(msg=_log_init(request=request))
101
-
102
- # process the callback operation
103
- errors: list[str] = []
104
- token_data: tuple[str, str] = _service_callback(registry=registry,
105
- args=request.args,
106
- errors=errors,
107
- logger=logger)
108
- # exchange the token
109
- if request.endpoint.startswith("jusbr-"):
110
- keycloak_registry: dict[str, Any] = __get_iam_registry(endpoint="keycloak-token")
111
- payload: dict[str, str] = {
112
- "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
113
- "subject_token": token_data[1],
114
- "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
115
- "client_id": keycloak_registry["client-id"],
116
- "client_secret": keycloak_registry["client-secret"],
117
- "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
118
- "audience": token_data[0]
119
- }
120
- exchange_url = f"{keycloak_registry['base-url']}/protocol/openid-connect/token"
121
- if logger:
122
- logger.debug(msg=f"POST '{exchange_url}', data {json.dumps(obj=payload,
123
- ensure_ascii=False)}")
124
- headers: dict[str, str] = {
125
- "Content-Type": "application/x-www-form-urlencoded"
126
- }
127
- response: requests.Response = requests.post(url=exchange_url,
128
- data=payload,
129
- headers=headers)
130
- if response.status_code == 200:
131
- # request succeeded
132
- if logger:
133
- logger.debug(msg=f"POST success, status {response.status_code}")
134
- reply: dict[str, Any] = response.json()
135
- token_data = (token_data[0], reply.get("access_token"))
136
- else:
137
- # request resulted in error
138
- err_msg = f"POST failure, status {response.status_code}, reason '{response.reason}'"
139
- if hasattr(response, "content") and response.content:
140
- err_msg += f", content '{response.content}'"
141
- errors.append(err_msg)
142
-
143
- result: Response
144
- if errors:
145
- result = jsonify({"errors": "; ".join(errors)})
146
- result.status_code = 400
147
- else:
148
- result = jsonify({
149
- "user-id": token_data[0],
150
- "access-token": token_data[1]})
151
-
152
- # log the response
153
- if logger:
154
- logger.debug(msg=f"Response {result}")
155
-
156
- return result
157
-
158
-
159
- # @flask_app.route(rule=<token_endpoint>, # JUSBR_TOKEN_ENDPOINT: /iam/jusbr:get-token
160
- # methods=["GET"])
161
- # @flask_app.route(rule=<token_endpoint>, # JUSBR_TOKEN_ENDPOINT: /iam/jusbr:get-token
162
- # methods=["GET"])
163
- def service_token() -> Response:
115
+ return {
116
+ "base_url": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_URL_AUTH_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_attribute": 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:
164
137
  """
165
- Entry point for retrieving the JusBR token.
138
+ Retrieve an authentication token for *user_id*.
166
139
 
167
- :return: the response containing the token, or *UNAUTHORIZED*
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
168
145
  """
169
- # retrieve logger and registry
170
- registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
171
- logger: Logger = registry["logger"]
172
-
173
- # log the request
174
- if logger:
175
- logger.debug(msg=_log_init(request=request))
146
+ # declare the return variable
147
+ result: str
176
148
 
177
149
  # retrieve the token
178
- errors: list[str] = []
179
- token: str = _service_token(registry=registry,
180
- args=request.args,
181
- errors=errors,
182
- logger=logger)
183
- result: Response
184
- if token:
185
- result = jsonify({"token": token})
186
- else:
187
- result = Response("; ".join(errors))
188
- result.status_code = 401
189
-
190
- # log the response
191
- if logger:
192
- logger.debug(msg=f"Response {result}")
193
-
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)
194
156
  return result
195
-
196
-
197
- def __get_iam_registry(endpoint: str) -> dict[str, Any]:
198
- """
199
- Retrieve the registry associated the the IAM identifies by *endpoint*.
200
-
201
- :param endpoint: the service enpoint identifying the IAM.
202
- :return: the tuple (*logger*, *registry*) associated with *endpoint*
203
- """
204
- # initialize the return variable
205
- result: dict[str, Any] | None = None
206
-
207
- if endpoint.startswith("jusbr-"):
208
- result = IAM_SERVERS[IamServer.IAM_JUSRBR]
209
- elif endpoint.startswith("keycloak-"):
210
- result = IAM_SERVERS[IamServer.IAM_KEYCLOAK]
211
-
212
- return result
213
-