pyxecm 3.0.1__py3-none-any.whl → 3.1.1__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 pyxecm might be problematic. Click here for more details.
- pyxecm/avts.py +4 -4
- pyxecm/coreshare.py +14 -15
- pyxecm/helper/data.py +2 -1
- pyxecm/helper/web.py +11 -11
- pyxecm/helper/xml.py +41 -10
- pyxecm/otac.py +1 -1
- pyxecm/otawp.py +19 -19
- pyxecm/otca.py +878 -70
- pyxecm/otcs.py +1716 -349
- pyxecm/otds.py +332 -153
- pyxecm/otkd.py +4 -4
- pyxecm/otmm.py +1 -1
- pyxecm/otpd.py +246 -30
- {pyxecm-3.0.1.dist-info → pyxecm-3.1.1.dist-info}/METADATA +2 -1
- pyxecm-3.1.1.dist-info/RECORD +82 -0
- pyxecm_api/app.py +45 -35
- pyxecm_api/auth/functions.py +2 -2
- pyxecm_api/auth/router.py +2 -3
- pyxecm_api/common/functions.py +67 -12
- pyxecm_api/settings.py +0 -8
- pyxecm_api/terminal/router.py +1 -1
- pyxecm_api/v1_csai/router.py +33 -18
- pyxecm_customizer/browser_automation.py +161 -79
- pyxecm_customizer/customizer.py +43 -25
- pyxecm_customizer/guidewire.py +422 -8
- pyxecm_customizer/k8s.py +23 -27
- pyxecm_customizer/knowledge_graph.py +498 -20
- pyxecm_customizer/m365.py +45 -44
- pyxecm_customizer/payload.py +1723 -1188
- pyxecm_customizer/payload_list.py +3 -0
- pyxecm_customizer/salesforce.py +122 -79
- pyxecm_customizer/servicenow.py +27 -7
- pyxecm_customizer/settings.py +3 -1
- pyxecm_customizer/successfactors.py +2 -2
- pyxecm_customizer/translate.py +1 -1
- pyxecm-3.0.1.dist-info/RECORD +0 -96
- pyxecm_api/agents/__init__.py +0 -7
- pyxecm_api/agents/app.py +0 -13
- pyxecm_api/agents/functions.py +0 -119
- pyxecm_api/agents/models.py +0 -10
- pyxecm_api/agents/otcm_knowledgegraph/__init__.py +0 -1
- pyxecm_api/agents/otcm_knowledgegraph/functions.py +0 -85
- pyxecm_api/agents/otcm_knowledgegraph/models.py +0 -61
- pyxecm_api/agents/otcm_knowledgegraph/router.py +0 -74
- pyxecm_api/agents/otcm_user_agent/__init__.py +0 -1
- pyxecm_api/agents/otcm_user_agent/models.py +0 -20
- pyxecm_api/agents/otcm_user_agent/router.py +0 -65
- pyxecm_api/agents/otcm_workspace_agent/__init__.py +0 -1
- pyxecm_api/agents/otcm_workspace_agent/models.py +0 -40
- pyxecm_api/agents/otcm_workspace_agent/router.py +0 -200
- {pyxecm-3.0.1.dist-info → pyxecm-3.1.1.dist-info}/WHEEL +0 -0
- {pyxecm-3.0.1.dist-info → pyxecm-3.1.1.dist-info}/entry_points.txt +0 -0
pyxecm/otds.py
CHANGED
|
@@ -56,8 +56,8 @@ REQUEST_FORM_HEADERS = {
|
|
|
56
56
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
REQUEST_TIMEOUT = 60
|
|
60
|
-
REQUEST_RETRY_DELAY = 20
|
|
59
|
+
REQUEST_TIMEOUT = 60.0
|
|
60
|
+
REQUEST_RETRY_DELAY = 20.0
|
|
61
61
|
REQUEST_MAX_RETRIES = 2
|
|
62
62
|
|
|
63
63
|
default_logger = logging.getLogger(MODULE_NAME)
|
|
@@ -71,6 +71,7 @@ class OTDS:
|
|
|
71
71
|
_config = None
|
|
72
72
|
_cookie = None
|
|
73
73
|
_otds_ticket = None
|
|
74
|
+
_token = None
|
|
74
75
|
|
|
75
76
|
def __init__(
|
|
76
77
|
self,
|
|
@@ -79,7 +80,10 @@ class OTDS:
|
|
|
79
80
|
port: int,
|
|
80
81
|
username: str | None = None,
|
|
81
82
|
password: str | None = None,
|
|
83
|
+
client_id: str | None = None,
|
|
84
|
+
client_secret: str | None = None,
|
|
82
85
|
otds_ticket: str | None = None,
|
|
86
|
+
oauth_token: str | None = None,
|
|
83
87
|
bind_password: str | None = None,
|
|
84
88
|
admin_partition: str = "otds.admin",
|
|
85
89
|
logger: logging.Logger = default_logger,
|
|
@@ -97,8 +101,14 @@ class OTDS:
|
|
|
97
101
|
The OTDS user name. Optional if otds_ticket is provided.
|
|
98
102
|
password (str, optional):
|
|
99
103
|
The OTDS password. Optional if otds_ticket is provided.
|
|
104
|
+
client_id (str | None, optional):
|
|
105
|
+
Client ID for grant type authentication.
|
|
106
|
+
client_secret (str | None, optional):
|
|
107
|
+
Client secret for grant type authentication.
|
|
100
108
|
otds_ticket (str | None, optional):
|
|
101
|
-
|
|
109
|
+
Pre-known authentication ticket of OTDS.
|
|
110
|
+
oauth_token (str | None, optional):
|
|
111
|
+
Pre-known OAuth token for OTDS.
|
|
102
112
|
bind_password (str | None, optional): TODO
|
|
103
113
|
admin_partition (str, optional):
|
|
104
114
|
Name of the admin partition. Default is "otds.admin".
|
|
@@ -115,40 +125,22 @@ class OTDS:
|
|
|
115
125
|
# Initialize otdsConfig as an empty dictionary
|
|
116
126
|
otds_config = {}
|
|
117
127
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
otds_config["protocol"] = "http"
|
|
127
|
-
|
|
128
|
-
if port:
|
|
129
|
-
otds_config["port"] = port
|
|
130
|
-
else:
|
|
131
|
-
otds_config["port"] = 80
|
|
132
|
-
|
|
133
|
-
if username:
|
|
134
|
-
otds_config["username"] = username
|
|
135
|
-
else:
|
|
136
|
-
otds_config["username"] = "admin"
|
|
137
|
-
|
|
138
|
-
if password:
|
|
139
|
-
otds_config["password"] = password
|
|
140
|
-
else:
|
|
141
|
-
otds_config["password"] = ""
|
|
142
|
-
|
|
143
|
-
if bind_password:
|
|
144
|
-
otds_config["bindPassword"] = bind_password
|
|
145
|
-
else:
|
|
146
|
-
otds_config["bindPassword"] = ""
|
|
147
|
-
|
|
128
|
+
otds_config["hostname"] = hostname or "otds"
|
|
129
|
+
otds_config["protocol"] = protocol or "http"
|
|
130
|
+
otds_config["port"] = port or 80
|
|
131
|
+
otds_config["username"] = username or "admin"
|
|
132
|
+
otds_config["password"] = password or ""
|
|
133
|
+
otds_config["clientId"] = client_id or ""
|
|
134
|
+
otds_config["clientSecret"] = client_secret or ""
|
|
135
|
+
otds_config["bindPassword"] = bind_password or ""
|
|
148
136
|
otds_config["adminPartition"] = admin_partition
|
|
149
137
|
|
|
138
|
+
# If a pre-existing OTDS ticket is provided we use it:
|
|
150
139
|
if otds_ticket:
|
|
151
140
|
self._cookie = {"OTDSTicket": otds_ticket}
|
|
141
|
+
# If a pre-existing OAuth token is provided we use it:
|
|
142
|
+
if oauth_token:
|
|
143
|
+
self._token = oauth_token
|
|
152
144
|
|
|
153
145
|
otds_base_url = protocol + "://" + otds_config["hostname"]
|
|
154
146
|
if str(port) not in ["80", "443"]:
|
|
@@ -163,9 +155,11 @@ class OTDS:
|
|
|
163
155
|
otds_config["identityproviderprofiles"] = otds_rest_url + "/identityproviderprofiles"
|
|
164
156
|
otds_config["accessRoleUrl"] = otds_rest_url + "/accessroles"
|
|
165
157
|
otds_config["credentialUrl"] = otds_rest_url + "/authentication/credentials"
|
|
158
|
+
otds_config["tokenUrl"] = otds_rest_url + "/authentication/token"
|
|
159
|
+
otds_config["tokenInfoUrl"] = otds_rest_url + "/authentication/oauth/tokeninfo"
|
|
166
160
|
otds_config["ticketforuserUrl"] = otds_rest_url + "/authentication/ticketforuser"
|
|
167
161
|
otds_config["oauthClientUrl"] = otds_rest_url + "/oauthclients"
|
|
168
|
-
otds_config["
|
|
162
|
+
otds_config["oauthTokenUrl"] = otds_base_url + "/oauth2/token"
|
|
169
163
|
otds_config["resourceUrl"] = otds_rest_url + "/resources"
|
|
170
164
|
otds_config["licenseUrl"] = otds_rest_url + "/licensemanagement/licenses"
|
|
171
165
|
otds_config["usersUrl"] = otds_rest_url + "/users"
|
|
@@ -225,6 +219,71 @@ class OTDS:
|
|
|
225
219
|
|
|
226
220
|
# end method definition
|
|
227
221
|
|
|
222
|
+
def get_access_token(self) -> str | None:
|
|
223
|
+
"""Get the access token for OAuth2 authentication.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
str | None:
|
|
227
|
+
The access token, or None in case of an error.
|
|
228
|
+
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
return self._token
|
|
232
|
+
|
|
233
|
+
# end method definition
|
|
234
|
+
|
|
235
|
+
def set_access_token(self, token: str) -> str | None:
|
|
236
|
+
"""Get the access token for OAuth2 authentication.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
token (str):
|
|
240
|
+
The new token value.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
str | None:
|
|
244
|
+
The access token, or None in case of an error.
|
|
245
|
+
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
self._token = token
|
|
249
|
+
|
|
250
|
+
return self._token
|
|
251
|
+
|
|
252
|
+
# end method definition
|
|
253
|
+
|
|
254
|
+
def get_access_token_info(self, resource_id: str | None = None) -> str | None:
|
|
255
|
+
"""Get the access token information for OAuth2 authentication.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
str | None:
|
|
259
|
+
The access token, or None in case of an error.
|
|
260
|
+
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
request_url = self.config()["tokenInfoUrl"]
|
|
264
|
+
|
|
265
|
+
token_info_body = {"token": self._token}
|
|
266
|
+
if resource_id:
|
|
267
|
+
token_info_body["resourceId"] = resource_id
|
|
268
|
+
|
|
269
|
+
self.logger.debug(
|
|
270
|
+
"Get OAuth token info%s; calling -> %s",
|
|
271
|
+
" for resource -> '{}'".format(resource_id) if resource_id else "",
|
|
272
|
+
request_url,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
return self.do_request(
|
|
276
|
+
url=request_url,
|
|
277
|
+
method="POST",
|
|
278
|
+
json_data=token_info_body,
|
|
279
|
+
timeout=None,
|
|
280
|
+
failure_message="Failed to get OAuth token info{}".format(
|
|
281
|
+
" for resource -> '{}'".format(resource_id) if resource_id else ""
|
|
282
|
+
),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# end method definition
|
|
286
|
+
|
|
228
287
|
def credentials(self) -> dict:
|
|
229
288
|
"""Return the credentials (username + password).
|
|
230
289
|
|
|
@@ -241,6 +300,42 @@ class OTDS:
|
|
|
241
300
|
|
|
242
301
|
# end method definition
|
|
243
302
|
|
|
303
|
+
def client_credentials(
|
|
304
|
+
self, grant_type: str = "client_credentials", scope: str | None = None, **kwargs: dict[str, str]
|
|
305
|
+
) -> dict:
|
|
306
|
+
"""Return the client credentials (client_id + client_secret).
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
grant_type (str, optional):
|
|
310
|
+
The grant type for the client credentials. Optional.
|
|
311
|
+
Defaults to "client_credentials".
|
|
312
|
+
scope (str | None, optional):
|
|
313
|
+
The scope for the client credentials. Optional.
|
|
314
|
+
Use "otdsssoticket" to get a standard / legacy OTDS ticket.
|
|
315
|
+
**kwargs:
|
|
316
|
+
Additional keyword arguments to add to the credentials dictionary.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
dict:
|
|
320
|
+
The dictionary with client_id and client_secret.
|
|
321
|
+
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
cred = {
|
|
325
|
+
"grant_type": grant_type,
|
|
326
|
+
"client_id": self.config()["clientId"],
|
|
327
|
+
"client_secret": self.config()["clientSecret"],
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if scope:
|
|
331
|
+
cred["scope"] = scope
|
|
332
|
+
|
|
333
|
+
cred.update(dict(kwargs))
|
|
334
|
+
|
|
335
|
+
return cred
|
|
336
|
+
|
|
337
|
+
# end method definition
|
|
338
|
+
|
|
244
339
|
def base_url(self) -> str:
|
|
245
340
|
"""Return the base URL of OTDS.
|
|
246
341
|
|
|
@@ -379,7 +474,7 @@ class OTDS:
|
|
|
379
474
|
|
|
380
475
|
"""
|
|
381
476
|
|
|
382
|
-
return self.config()["
|
|
477
|
+
return self.config()["oauthTokenUrl"]
|
|
383
478
|
|
|
384
479
|
# end method definition
|
|
385
480
|
|
|
@@ -461,6 +556,41 @@ class OTDS:
|
|
|
461
556
|
|
|
462
557
|
# end method definition
|
|
463
558
|
|
|
559
|
+
def request_header(self, content_type: str = "application/json") -> dict:
|
|
560
|
+
"""Return the request header used for requests.
|
|
561
|
+
|
|
562
|
+
Consists of Bearer access token and Content Type
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
service_type (str, optional):
|
|
566
|
+
Service type for which the header should be returned.
|
|
567
|
+
Either "chat" or "embed". "chat" is the default.
|
|
568
|
+
|
|
569
|
+
content_type (str, optional):
|
|
570
|
+
Custom content type for the request.
|
|
571
|
+
Typical values:
|
|
572
|
+
* application/json - Used for sending JSON-encoded data
|
|
573
|
+
* application/x-www-form-urlencoded - The default for HTML forms.
|
|
574
|
+
Data is sent as key-value pairs in the body of the request, similar to query parameters.
|
|
575
|
+
* multipart/form-data - Used for file uploads or when a form includes non-ASCII characters
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
dict: The request header values.
|
|
579
|
+
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
request_header = REQUEST_HEADERS
|
|
583
|
+
|
|
584
|
+
if content_type:
|
|
585
|
+
request_header["Content-Type"] = content_type
|
|
586
|
+
|
|
587
|
+
if self._token is not None:
|
|
588
|
+
request_header["Authorization"] = "Bearer {}".format(self._token)
|
|
589
|
+
|
|
590
|
+
return request_header
|
|
591
|
+
|
|
592
|
+
# end method definition
|
|
593
|
+
|
|
464
594
|
def do_request(
|
|
465
595
|
self,
|
|
466
596
|
url: str,
|
|
@@ -469,7 +599,7 @@ class OTDS:
|
|
|
469
599
|
data: dict | None = None,
|
|
470
600
|
json_data: dict | None = None,
|
|
471
601
|
files: dict | None = None,
|
|
472
|
-
timeout:
|
|
602
|
+
timeout: float | None = REQUEST_TIMEOUT,
|
|
473
603
|
show_error: bool = True,
|
|
474
604
|
show_warning: bool = False,
|
|
475
605
|
warning_message: str = "",
|
|
@@ -495,7 +625,7 @@ class OTDS:
|
|
|
495
625
|
files (dict | None, optional):
|
|
496
626
|
Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
497
627
|
File-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
498
|
-
timeout (
|
|
628
|
+
timeout (float | None, optional):
|
|
499
629
|
The timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
500
630
|
show_error (bool, optional):
|
|
501
631
|
Whether or not an error should be logged in case of a failed REST call.
|
|
@@ -525,7 +655,7 @@ class OTDS:
|
|
|
525
655
|
"""
|
|
526
656
|
|
|
527
657
|
if headers is None:
|
|
528
|
-
headers =
|
|
658
|
+
headers = self.request_header()
|
|
529
659
|
|
|
530
660
|
# In case of an expired session we reauthenticate and
|
|
531
661
|
# try 1 more time. Session expiration should not happen
|
|
@@ -620,8 +750,11 @@ class OTDS:
|
|
|
620
750
|
except requests.exceptions.ConnectionError:
|
|
621
751
|
if retries <= max_retries:
|
|
622
752
|
self.logger.warning(
|
|
623
|
-
"Connection error
|
|
624
|
-
|
|
753
|
+
"Connection error (%s)! Retrying in %d seconds... %d/%d",
|
|
754
|
+
url,
|
|
755
|
+
REQUEST_RETRY_DELAY,
|
|
756
|
+
retries,
|
|
757
|
+
max_retries,
|
|
625
758
|
)
|
|
626
759
|
retries += 1
|
|
627
760
|
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
@@ -698,13 +831,21 @@ class OTDS:
|
|
|
698
831
|
|
|
699
832
|
# end method definition
|
|
700
833
|
|
|
701
|
-
def authenticate(self, revalidate: bool = False) -> dict | None:
|
|
834
|
+
def authenticate(self, revalidate: bool = False, grant_type: str | None = None) -> dict | None:
|
|
702
835
|
"""Authenticate at Directory Services and retrieve OTDS ticket.
|
|
703
836
|
|
|
704
837
|
Args:
|
|
705
838
|
revalidate (bool, optional):
|
|
706
839
|
Determine if a re-athentication is enforced.
|
|
707
840
|
(e.g. if session has timed out with 401 error)
|
|
841
|
+
grant_type (str | None, optional):
|
|
842
|
+
The grant type to use for authentication.
|
|
843
|
+
Possible values are "password", "client_credentials", or None. Defaults to None.
|
|
844
|
+
If None is given, the method tries to determine the grant type automatically:
|
|
845
|
+
* If username and password are given, "password" is used.
|
|
846
|
+
* If client_id and client_secret are given, "client_credentials" is used.
|
|
847
|
+
* If both are given, "password" is used.
|
|
848
|
+
* If none of the above is given, an error is logged and None is returned.
|
|
708
849
|
|
|
709
850
|
Returns:
|
|
710
851
|
dict | None:
|
|
@@ -722,14 +863,44 @@ class OTDS:
|
|
|
722
863
|
|
|
723
864
|
otds_ticket = "NotSet"
|
|
724
865
|
|
|
725
|
-
self.logger.debug(
|
|
866
|
+
self.logger.debug(
|
|
867
|
+
"Requesting OTDS ticket from -> %s using grant type -> '%s'...", self.credential_url(), grant_type
|
|
868
|
+
)
|
|
726
869
|
|
|
727
870
|
response = None
|
|
871
|
+
|
|
872
|
+
if not grant_type:
|
|
873
|
+
if self.config()["username"] and self.config()["password"]:
|
|
874
|
+
grant_type = "password"
|
|
875
|
+
elif self.config()["clientId"] and self.config()["clientSecret"]:
|
|
876
|
+
grant_type = "client_credentials"
|
|
877
|
+
else:
|
|
878
|
+
self.logger.error(
|
|
879
|
+
"Cannot determine grant type automatically - please provide username/password or client_id/client_secret for authentication."
|
|
880
|
+
)
|
|
881
|
+
return None
|
|
882
|
+
|
|
728
883
|
try:
|
|
884
|
+
if grant_type == "client_credentials":
|
|
885
|
+
request_url = self.token_url()
|
|
886
|
+
headers = REQUEST_FORM_HEADERS
|
|
887
|
+
data = self.client_credentials()
|
|
888
|
+
json_data = None
|
|
889
|
+
result_value = "access_token"
|
|
890
|
+
elif grant_type == "password":
|
|
891
|
+
request_url = self.credential_url()
|
|
892
|
+
headers = REQUEST_HEADERS
|
|
893
|
+
data = None
|
|
894
|
+
json_data = self.credentials()
|
|
895
|
+
result_value = "ticket"
|
|
896
|
+
else:
|
|
897
|
+
self.logger.error("Unsupported grant type -> '%s' - exit", grant_type)
|
|
898
|
+
return None
|
|
729
899
|
response = requests.post(
|
|
730
|
-
url=
|
|
731
|
-
|
|
732
|
-
|
|
900
|
+
url=request_url,
|
|
901
|
+
headers=headers,
|
|
902
|
+
data=data,
|
|
903
|
+
json=json_data,
|
|
733
904
|
timeout=REQUEST_TIMEOUT,
|
|
734
905
|
)
|
|
735
906
|
except requests.exceptions.RequestException as exception:
|
|
@@ -745,20 +916,27 @@ class OTDS:
|
|
|
745
916
|
if not authenticate_dict:
|
|
746
917
|
return None
|
|
747
918
|
else:
|
|
748
|
-
otds_ticket = authenticate_dict[
|
|
749
|
-
self.logger.debug("Ticket -> %s", otds_ticket)
|
|
919
|
+
otds_ticket = authenticate_dict[result_value]
|
|
920
|
+
self.logger.debug("Ticket / token -> %s", otds_ticket)
|
|
750
921
|
else:
|
|
751
922
|
self.logger.error(
|
|
752
|
-
"Failed to request an OTDS ticket; error -> %s",
|
|
923
|
+
"Failed to request an OTDS ticket / access token; error -> %s",
|
|
753
924
|
response.text,
|
|
754
925
|
)
|
|
755
926
|
return None
|
|
756
927
|
|
|
757
928
|
# Store authentication ticket:
|
|
758
|
-
self._cookie = {"OTDSTicket": otds_ticket}
|
|
759
929
|
self._otds_ticket = otds_ticket
|
|
930
|
+
if grant_type == "password":
|
|
931
|
+
self._cookie = {"OTDSTicket": otds_ticket}
|
|
932
|
+
self._token = None
|
|
933
|
+
return self._cookie
|
|
934
|
+
elif grant_type == "client_credentials":
|
|
935
|
+
self._token = otds_ticket
|
|
936
|
+
self._cookie = None
|
|
937
|
+
return self._token
|
|
760
938
|
|
|
761
|
-
return
|
|
939
|
+
return None
|
|
762
940
|
|
|
763
941
|
# end method definition
|
|
764
942
|
|
|
@@ -783,7 +961,7 @@ class OTDS:
|
|
|
783
961
|
dict | None:
|
|
784
962
|
Information about the impersonated user.
|
|
785
963
|
|
|
786
|
-
Example:
|
|
964
|
+
Example ticket based response:
|
|
787
965
|
{
|
|
788
966
|
'token': None,
|
|
789
967
|
'userId': 'nwheeler@Content Server Members',
|
|
@@ -795,32 +973,69 @@ class OTDS:
|
|
|
795
973
|
'continuationContext': None,
|
|
796
974
|
'continuationData': None
|
|
797
975
|
}
|
|
976
|
+
Example token based response:
|
|
977
|
+
{
|
|
978
|
+
'access_token': 'eyJraWQiOiJ...',
|
|
979
|
+
'issued_token_type': 'urn:ietf:params:oauth:token-type:access_token',
|
|
980
|
+
'token_type': 'Bearer',
|
|
981
|
+
'expires_in': 3600
|
|
982
|
+
}
|
|
798
983
|
|
|
799
984
|
"""
|
|
800
985
|
|
|
801
|
-
if
|
|
802
|
-
|
|
986
|
+
# Check if we have a token-based authentication:
|
|
987
|
+
if self._token is not None:
|
|
988
|
+
request_url = self.token_url()
|
|
803
989
|
|
|
804
|
-
|
|
990
|
+
impersonate_post_body = self.client_credentials(
|
|
991
|
+
scope=None,
|
|
992
|
+
grant_type="urn:ietf:params:oauth:grant-type:token-exchange",
|
|
993
|
+
subject_token_type="urn:opentext.com:oauth:string:user_id",
|
|
994
|
+
subject_token=user_id + "@" + partition,
|
|
995
|
+
)
|
|
805
996
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
997
|
+
self.logger.debug(
|
|
998
|
+
"Impersonate user -> '%s' with token -> '%s'; calling -> %s",
|
|
999
|
+
user_id,
|
|
1000
|
+
self._token,
|
|
1001
|
+
request_url,
|
|
1002
|
+
)
|
|
810
1003
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
1004
|
+
response = self.do_request(
|
|
1005
|
+
url=request_url,
|
|
1006
|
+
method="POST",
|
|
1007
|
+
headers=REQUEST_FORM_HEADERS,
|
|
1008
|
+
data=impersonate_post_body,
|
|
1009
|
+
timeout=None,
|
|
1010
|
+
failure_message="Failed to impersonate as user -> '{}'".format(user_id),
|
|
1011
|
+
)
|
|
1012
|
+
else: # ticket-based authentication
|
|
1013
|
+
if not ticket:
|
|
1014
|
+
ticket = self._otds_ticket
|
|
816
1015
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1016
|
+
request_url = self.config()["ticketforuserUrl"]
|
|
1017
|
+
|
|
1018
|
+
impersonate_post_body = {
|
|
1019
|
+
"userName": user_id + "@" + partition,
|
|
1020
|
+
"ticket": ticket,
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
self.logger.debug(
|
|
1024
|
+
"Impersonate user -> '%s' with ticket -> '%s'; calling -> %s",
|
|
1025
|
+
user_id,
|
|
1026
|
+
ticket,
|
|
1027
|
+
request_url,
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
response = self.do_request(
|
|
1031
|
+
url=request_url,
|
|
1032
|
+
method="POST",
|
|
1033
|
+
json_data=impersonate_post_body,
|
|
1034
|
+
timeout=None,
|
|
1035
|
+
failure_message="Failed to impersonate as user -> '{}'".format(user_id),
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
return response
|
|
824
1039
|
|
|
825
1040
|
# end method definition
|
|
826
1041
|
|
|
@@ -903,8 +1118,9 @@ class OTDS:
|
|
|
903
1118
|
request_url = "{}?where_filter={}".format(self.config()["rolesUrl"], name)
|
|
904
1119
|
|
|
905
1120
|
self.logger.debug(
|
|
906
|
-
"Get application
|
|
1121
|
+
"Get application role -> '%s' in partition -> '%s'; calling -> %s",
|
|
907
1122
|
name,
|
|
1123
|
+
partition,
|
|
908
1124
|
request_url,
|
|
909
1125
|
)
|
|
910
1126
|
|
|
@@ -912,7 +1128,7 @@ class OTDS:
|
|
|
912
1128
|
url=request_url,
|
|
913
1129
|
method="GET",
|
|
914
1130
|
timeout=None,
|
|
915
|
-
failure_message="Failed to get
|
|
1131
|
+
failure_message="Failed to get application role -> '{}' in partition -> '{}'".format(name, partition),
|
|
916
1132
|
show_error=show_error,
|
|
917
1133
|
)
|
|
918
1134
|
|
|
@@ -954,30 +1170,35 @@ class OTDS:
|
|
|
954
1170
|
if user:
|
|
955
1171
|
user_location = user["location"]
|
|
956
1172
|
else:
|
|
957
|
-
self.logger.error(
|
|
1173
|
+
self.logger.error(
|
|
1174
|
+
"Cannot find user -> '%s' in partition -> '%s'! Cannot assign user to application role -> '%s'.",
|
|
1175
|
+
user_id,
|
|
1176
|
+
user_partition,
|
|
1177
|
+
role_name,
|
|
1178
|
+
)
|
|
958
1179
|
return False
|
|
959
1180
|
|
|
960
|
-
role = self.get_application_role(role_name, role_partition)
|
|
1181
|
+
role = self.get_application_role(name=role_name, partition=role_partition)
|
|
961
1182
|
if role:
|
|
962
|
-
|
|
1183
|
+
role_location = role.get("location")
|
|
963
1184
|
else:
|
|
964
|
-
self.logger.warning("Cannot find application role -> '%s'
|
|
1185
|
+
self.logger.warning("Cannot find application role -> '%s' in partition -> '%s'!", role_name, role_partition)
|
|
965
1186
|
return False
|
|
966
1187
|
|
|
967
1188
|
role_post_body_json = {
|
|
968
1189
|
"stringList": [
|
|
969
|
-
|
|
1190
|
+
role_location,
|
|
970
1191
|
],
|
|
971
1192
|
}
|
|
972
1193
|
|
|
973
1194
|
request_url = self.users_url() + "/" + user_location + "/roles"
|
|
974
1195
|
|
|
975
1196
|
self.logger.debug(
|
|
976
|
-
"Assign
|
|
977
|
-
role_name,
|
|
978
|
-
role_partition,
|
|
1197
|
+
"Assign user -> '%s' (%s) to application role -> '%s' (%s); calling -> %s",
|
|
979
1198
|
user_id,
|
|
980
1199
|
user_partition,
|
|
1200
|
+
role_name,
|
|
1201
|
+
role_partition,
|
|
981
1202
|
request_url,
|
|
982
1203
|
)
|
|
983
1204
|
|
|
@@ -986,18 +1207,18 @@ class OTDS:
|
|
|
986
1207
|
method="POST",
|
|
987
1208
|
json_data=role_post_body_json,
|
|
988
1209
|
timeout=None,
|
|
989
|
-
failure_message="Failed to assign
|
|
990
|
-
role_name,
|
|
1210
|
+
failure_message="Failed to assign user -> '{}' to application role -> '{}'!".format(
|
|
991
1211
|
user_id,
|
|
1212
|
+
role_name,
|
|
992
1213
|
),
|
|
993
1214
|
parse_request_response=False,
|
|
994
1215
|
)
|
|
995
1216
|
|
|
996
1217
|
if response and response.ok:
|
|
997
1218
|
self.logger.debug(
|
|
998
|
-
"Added
|
|
999
|
-
role_name,
|
|
1219
|
+
"Added user -> '%s' to application role -> '%s'.",
|
|
1000
1220
|
user_id,
|
|
1221
|
+
role_name,
|
|
1001
1222
|
)
|
|
1002
1223
|
return True
|
|
1003
1224
|
|
|
@@ -1030,34 +1251,36 @@ class OTDS:
|
|
|
1030
1251
|
|
|
1031
1252
|
"""
|
|
1032
1253
|
|
|
1033
|
-
group = self.get_group(group_id)
|
|
1254
|
+
group = self.get_group(group=group_id)
|
|
1034
1255
|
if group:
|
|
1035
1256
|
group_location = group["location"]
|
|
1036
1257
|
else:
|
|
1037
|
-
self.logger.error(
|
|
1258
|
+
self.logger.error(
|
|
1259
|
+
"Cannot find group -> '%s'! Cannot assign group to application role -> '%s'.", group_id, role_name
|
|
1260
|
+
)
|
|
1038
1261
|
return False
|
|
1039
1262
|
|
|
1040
|
-
role = self.get_application_role(role_name, role_partition)
|
|
1263
|
+
role = self.get_application_role(name=role_name, partition=role_partition)
|
|
1041
1264
|
if role:
|
|
1042
|
-
|
|
1265
|
+
role_location = role.get("location")
|
|
1043
1266
|
else:
|
|
1044
|
-
self.logger.warning("Cannot find application role -> '%s'
|
|
1267
|
+
self.logger.warning("Cannot find application role -> '%s' in partition -> '%s'!", role_name, role_partition)
|
|
1045
1268
|
return False
|
|
1046
1269
|
|
|
1047
1270
|
role_post_body_json = {
|
|
1048
1271
|
"stringList": [
|
|
1049
|
-
|
|
1272
|
+
role_location,
|
|
1050
1273
|
],
|
|
1051
1274
|
}
|
|
1052
1275
|
|
|
1053
1276
|
request_url = self.groups_url() + "/" + group_location + "/roles"
|
|
1054
1277
|
|
|
1055
1278
|
self.logger.debug(
|
|
1056
|
-
"Assign
|
|
1057
|
-
role_name,
|
|
1058
|
-
role_partition,
|
|
1279
|
+
"Assign group -> '%s' (%s) to application role -> '%s' (%s); calling -> %s",
|
|
1059
1280
|
group_id,
|
|
1060
1281
|
group_partition,
|
|
1282
|
+
role_name,
|
|
1283
|
+
role_partition,
|
|
1061
1284
|
request_url,
|
|
1062
1285
|
)
|
|
1063
1286
|
|
|
@@ -1895,7 +2118,7 @@ class OTDS:
|
|
|
1895
2118
|
str(page_size),
|
|
1896
2119
|
request_url,
|
|
1897
2120
|
)
|
|
1898
|
-
failure_message = "Failed to get all groups in partition -> '{}'".format(
|
|
2121
|
+
failure_message = "Failed to get all groups in partition -> '{}'!".format(
|
|
1899
2122
|
partition,
|
|
1900
2123
|
)
|
|
1901
2124
|
else:
|
|
@@ -2502,7 +2725,7 @@ class OTDS:
|
|
|
2502
2725
|
for access_role_group in access_role_groups:
|
|
2503
2726
|
if access_role_group["name"] == group:
|
|
2504
2727
|
self.logger.debug(
|
|
2505
|
-
"Group -> '%s' already added to access role -> '%s'",
|
|
2728
|
+
"Group -> '%s' already added to access role -> '%s'.",
|
|
2506
2729
|
group,
|
|
2507
2730
|
access_role,
|
|
2508
2731
|
)
|
|
@@ -2579,7 +2802,7 @@ class OTDS:
|
|
|
2579
2802
|
# create payload for REST call:
|
|
2580
2803
|
access_role = self.get_access_role(name)
|
|
2581
2804
|
if not access_role:
|
|
2582
|
-
self.logger.error("Failed to get access role -> '%s'", name)
|
|
2805
|
+
self.logger.error("Failed to get access role -> '%s'! Cannot update its attributes.", name)
|
|
2583
2806
|
return None
|
|
2584
2807
|
|
|
2585
2808
|
access_role_put_body_json = {"attributes": attribute_list}
|
|
@@ -2841,7 +3064,7 @@ class OTDS:
|
|
|
2841
3064
|
licenses = self.get_license_for_resource(resource_id)
|
|
2842
3065
|
if not licenses:
|
|
2843
3066
|
self.logger.error(
|
|
2844
|
-
"Resource with ID -> '%s' does not exist or has no licenses",
|
|
3067
|
+
"Resource with ID -> '%s' does not exist or has no licenses!",
|
|
2845
3068
|
resource_id,
|
|
2846
3069
|
)
|
|
2847
3070
|
return False
|
|
@@ -2852,7 +3075,7 @@ class OTDS:
|
|
|
2852
3075
|
break
|
|
2853
3076
|
else:
|
|
2854
3077
|
self.logger.error(
|
|
2855
|
-
"Cannot find license -> '%s' for resource -> %s",
|
|
3078
|
+
"Cannot find license -> '%s' for resource with ID -> '%s'!",
|
|
2856
3079
|
license_name,
|
|
2857
3080
|
resource_id,
|
|
2858
3081
|
)
|
|
@@ -2862,7 +3085,7 @@ class OTDS:
|
|
|
2862
3085
|
if user:
|
|
2863
3086
|
user_location = user["location"]
|
|
2864
3087
|
else:
|
|
2865
|
-
self.logger.error("Cannot find location for user -> '%s'", user_id)
|
|
3088
|
+
self.logger.error("Cannot find location for user -> '%s'!", user_id)
|
|
2866
3089
|
return False
|
|
2867
3090
|
|
|
2868
3091
|
license_post_body_json = {
|
|
@@ -2888,7 +3111,7 @@ class OTDS:
|
|
|
2888
3111
|
method="POST",
|
|
2889
3112
|
json_data=license_post_body_json,
|
|
2890
3113
|
timeout=None,
|
|
2891
|
-
failure_message="Failed to add license feature -> '{}' associated with resource -> '{}' to user -> '{}'".format(
|
|
3114
|
+
failure_message="Failed to add license feature -> '{}' associated with resource ID -> '{}' to user -> '{}'".format(
|
|
2892
3115
|
license_feature,
|
|
2893
3116
|
resource_id,
|
|
2894
3117
|
user_id,
|
|
@@ -2898,7 +3121,7 @@ class OTDS:
|
|
|
2898
3121
|
|
|
2899
3122
|
if response and response.ok:
|
|
2900
3123
|
self.logger.debug(
|
|
2901
|
-
"Added license feature -> '%s' to user -> '%s'",
|
|
3124
|
+
"Added license feature -> '%s' to user -> '%s'.",
|
|
2902
3125
|
license_feature,
|
|
2903
3126
|
user_id,
|
|
2904
3127
|
)
|
|
@@ -2952,7 +3175,7 @@ class OTDS:
|
|
|
2952
3175
|
licenses = self.get_license_for_resource(resource_id)
|
|
2953
3176
|
if not licenses:
|
|
2954
3177
|
self.logger.error(
|
|
2955
|
-
"Resource with ID -> '%s' does not exist or has no licenses",
|
|
3178
|
+
"Resource with ID -> '%s' does not exist or has no licenses!",
|
|
2956
3179
|
resource_id,
|
|
2957
3180
|
)
|
|
2958
3181
|
return False
|
|
@@ -2992,7 +3215,7 @@ class OTDS:
|
|
|
2992
3215
|
method="POST",
|
|
2993
3216
|
json_data=license_post_body_json,
|
|
2994
3217
|
timeout=None,
|
|
2995
|
-
failure_message="Failed to add license feature -> '{}' associated with resource -> '{}' to partition -> '{}'".format(
|
|
3218
|
+
failure_message="Failed to add license feature -> '{}' associated with resource ID -> '{}' to partition -> '{}'".format(
|
|
2996
3219
|
license_feature,
|
|
2997
3220
|
resource_id,
|
|
2998
3221
|
partition_name,
|
|
@@ -3080,7 +3303,7 @@ class OTDS:
|
|
|
3080
3303
|
licenses = self.get_license_for_resource(resource_id)
|
|
3081
3304
|
if not licenses:
|
|
3082
3305
|
self.logger.error(
|
|
3083
|
-
"Resource with ID -> '%s' does not exist or has no licenses",
|
|
3306
|
+
"Resource with ID -> '%s' does not exist or has no licenses!",
|
|
3084
3307
|
resource_id,
|
|
3085
3308
|
)
|
|
3086
3309
|
return False
|
|
@@ -3823,7 +4046,7 @@ class OTDS:
|
|
|
3823
4046
|
for user_partition in user_partitions:
|
|
3824
4047
|
if user_partition["userPartition"] == "OAuthClients":
|
|
3825
4048
|
self.logger.error(
|
|
3826
|
-
"OAuthClients partition already added to role -> %s",
|
|
4049
|
+
"OAuthClients partition already added to role -> '%s'!",
|
|
3827
4050
|
access_role_name,
|
|
3828
4051
|
)
|
|
3829
4052
|
return None
|
|
@@ -3869,50 +4092,6 @@ class OTDS:
|
|
|
3869
4092
|
|
|
3870
4093
|
# end method definition
|
|
3871
4094
|
|
|
3872
|
-
def get_access_token(self, client_id: str, client_secret: str) -> str | None:
|
|
3873
|
-
"""Get the access token.
|
|
3874
|
-
|
|
3875
|
-
Args:
|
|
3876
|
-
client_id (str):
|
|
3877
|
-
The OAuth client name (= ID).
|
|
3878
|
-
client_secret (str):
|
|
3879
|
-
The OAuth client secret. This is typically returned
|
|
3880
|
-
by add_oauth_client() method in ["secret"] field.
|
|
3881
|
-
|
|
3882
|
-
Returns:
|
|
3883
|
-
str | None:
|
|
3884
|
-
The access token, or None in case of an error.
|
|
3885
|
-
|
|
3886
|
-
"""
|
|
3887
|
-
|
|
3888
|
-
encoded_client_secret = "{}:{}".format(client_id, client_secret).encode("utf-8")
|
|
3889
|
-
|
|
3890
|
-
request_header = {
|
|
3891
|
-
"Authorization": "Basic " + base64.b64encode(encoded_client_secret).decode("utf-8"),
|
|
3892
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
3893
|
-
}
|
|
3894
|
-
request_url = self.token_url()
|
|
3895
|
-
|
|
3896
|
-
response = requests.post(
|
|
3897
|
-
url=request_url,
|
|
3898
|
-
data={"grant_type": "client_credentials"},
|
|
3899
|
-
headers=request_header,
|
|
3900
|
-
timeout=REQUEST_TIMEOUT,
|
|
3901
|
-
)
|
|
3902
|
-
|
|
3903
|
-
access_token = None
|
|
3904
|
-
if response.ok:
|
|
3905
|
-
access_token = self.parse_request_response(response)
|
|
3906
|
-
|
|
3907
|
-
if "access_token" in access_token:
|
|
3908
|
-
access_token = access_token["access_token"]
|
|
3909
|
-
else:
|
|
3910
|
-
return None
|
|
3911
|
-
|
|
3912
|
-
return access_token
|
|
3913
|
-
|
|
3914
|
-
# end method definition
|
|
3915
|
-
|
|
3916
4095
|
def get_auth_handler(self, name: str, show_error: bool = True) -> dict | None:
|
|
3917
4096
|
"""Get the OTDS auth handler with a given name.
|
|
3918
4097
|
|
|
@@ -4457,7 +4636,7 @@ class OTDS:
|
|
|
4457
4636
|
cert_content = cert_file.read()
|
|
4458
4637
|
if not cert_content:
|
|
4459
4638
|
self.logger.error(
|
|
4460
|
-
"No data in certificate file -> '%s'",
|
|
4639
|
+
"No data in certificate file -> '%s'!",
|
|
4461
4640
|
certificate_file,
|
|
4462
4641
|
)
|
|
4463
4642
|
return None
|
|
@@ -4486,7 +4665,7 @@ class OTDS:
|
|
|
4486
4665
|
cert_file_encoded = False
|
|
4487
4666
|
except TypeError:
|
|
4488
4667
|
self.logger.debug(
|
|
4489
|
-
"Certificate file -> '%s' is not base64 encoded",
|
|
4668
|
+
"Certificate file -> '%s' is not base64 encoded!",
|
|
4490
4669
|
certificate_file,
|
|
4491
4670
|
)
|
|
4492
4671
|
cert_file_encoded = False
|
|
@@ -4991,14 +5170,14 @@ class OTDS:
|
|
|
4991
5170
|
resource = self.get_resource(resource_name)
|
|
4992
5171
|
if not resource:
|
|
4993
5172
|
self.logger.error(
|
|
4994
|
-
"Resource -> '%s' not found - cannot consolidate",
|
|
5173
|
+
"Resource -> '%s' not found - cannot consolidate!",
|
|
4995
5174
|
resource_name,
|
|
4996
5175
|
)
|
|
4997
5176
|
return False
|
|
4998
5177
|
|
|
4999
5178
|
resource_dn = resource["resourceDN"]
|
|
5000
5179
|
if not resource_dn:
|
|
5001
|
-
self.logger.error("Resource DN is empty - cannot consolidate")
|
|
5180
|
+
self.logger.error("Resource DN is empty - cannot consolidate!")
|
|
5002
5181
|
return False
|
|
5003
5182
|
|
|
5004
5183
|
consolidation_post_body_json = {
|