pyxecm 1.4__py3-none-any.whl → 1.6__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/__init__.py +5 -0
- pyxecm/avts.py +1065 -0
- pyxecm/coreshare.py +2532 -0
- pyxecm/customizer/__init__.py +4 -0
- pyxecm/customizer/browser_automation.py +164 -54
- pyxecm/customizer/customizer.py +588 -231
- pyxecm/customizer/k8s.py +143 -29
- pyxecm/customizer/m365.py +1434 -1323
- pyxecm/customizer/payload.py +15073 -5933
- pyxecm/customizer/pht.py +926 -0
- pyxecm/customizer/salesforce.py +866 -351
- pyxecm/customizer/sap.py +4 -4
- pyxecm/customizer/servicenow.py +1467 -0
- pyxecm/customizer/successfactors.py +1056 -0
- pyxecm/helper/__init__.py +2 -0
- pyxecm/helper/assoc.py +44 -1
- pyxecm/helper/data.py +1731 -0
- pyxecm/helper/web.py +170 -46
- pyxecm/helper/xml.py +170 -34
- pyxecm/otac.py +309 -23
- pyxecm/otawp.py +1810 -0
- pyxecm/otcs.py +5308 -2985
- pyxecm/otds.py +1909 -1954
- pyxecm/otmm.py +928 -0
- pyxecm/otpd.py +13 -10
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/METADATA +5 -1
- pyxecm-1.6.dist-info/RECORD +32 -0
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/WHEEL +1 -1
- pyxecm-1.4.dist-info/RECORD +0 -24
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/LICENSE +0 -0
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/top_level.txt +0 -0
pyxecm/otds.py
CHANGED
|
@@ -27,30 +27,22 @@ groups_url : returns OTDS Groups REST URL
|
|
|
27
27
|
system_config_url : returns OTDS System Config REST URL
|
|
28
28
|
consolidation_url: returns OTDS consolidation URL
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
do_request: call an OTDS REST API in a safe way.
|
|
31
|
+
parse_request_response: Converts the request response to a Python dict in a safe way
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
get_license_for_resource : Get list of licenses for a resource
|
|
34
|
-
delete_license_from_resource : Delete a license from a resource
|
|
35
|
-
assign_user_to_license : Assign an OTDS user to a product license (feature) in OTDS.
|
|
36
|
-
assign_partition_to_license: Assign an OTDS user partition to a license (feature) in OTDS.
|
|
37
|
-
get_licensed_objects: Return the licensed objects (users, groups, partitions) an OTDS for a
|
|
38
|
-
license + license feature associated with an OTDS resource (like "cs").
|
|
39
|
-
is_user_licensed: Check if a user is licensed for a license and license feature associated
|
|
40
|
-
with a particular OTDS resource.
|
|
41
|
-
is_group_licensed: Check if a group is licensed for a license and license feature associated
|
|
42
|
-
with a particular OTDS resource.
|
|
43
|
-
is_partition_licensed: Check if a user partition is licensed for a license and license feature
|
|
44
|
-
associated with a particular OTDS resource.
|
|
33
|
+
authenticate : authenticates at OTDS server
|
|
45
34
|
|
|
35
|
+
add_synchronized_partition: Add a Synchronized partition to OTDS
|
|
46
36
|
add_partition : Add an OTDS partition
|
|
47
37
|
get_partition : Get a partition with a specific name
|
|
38
|
+
|
|
48
39
|
add_user : Add a user to a partion
|
|
49
40
|
get_user : Get a user with a specific user ID (= login name @ partition)
|
|
50
41
|
get_users: get all users (with option to filter)
|
|
51
42
|
update_user : Update attributes of on OTDS user
|
|
52
43
|
delete_user : Delete a user with a specific ID in a specific partition
|
|
53
44
|
reset_user_password : Reset a password of a specific user ID
|
|
45
|
+
|
|
54
46
|
add_group: Add an OTDS group
|
|
55
47
|
get_group: Get a OTDS group by its name
|
|
56
48
|
add_user_to_group : Add an OTDS user to a OTDS group
|
|
@@ -68,6 +60,20 @@ add_user_to_access_role : Add an OTDS user to to an OTDS Access Role
|
|
|
68
60
|
add_group_to_access_role : Add an OTDS group to to an OTDS Access Role
|
|
69
61
|
update_access_role_attributes: Update attributes of an existing access role
|
|
70
62
|
|
|
63
|
+
add_license_to_resource : Add (or update) a product license to OTDS
|
|
64
|
+
get_license_for_resource : Get list of licenses for a resource
|
|
65
|
+
delete_license_from_resource : Delete a license from a resource
|
|
66
|
+
assign_user_to_license : Assign an OTDS user to a product license (feature) in OTDS.
|
|
67
|
+
assign_partition_to_license: Assign an OTDS user partition to a license (feature) in OTDS.
|
|
68
|
+
get_licensed_objects: Return the licensed objects (users, groups, partitions) an OTDS for a
|
|
69
|
+
license + license feature associated with an OTDS resource (like "cs").
|
|
70
|
+
is_user_licensed: Check if a user is licensed for a license and license feature associated
|
|
71
|
+
with a particular OTDS resource.
|
|
72
|
+
is_group_licensed: Check if a group is licensed for a license and license feature associated
|
|
73
|
+
with a particular OTDS resource.
|
|
74
|
+
is_partition_licensed: Check if a user partition is licensed for a license and license feature
|
|
75
|
+
associated with a particular OTDS resource.
|
|
76
|
+
|
|
71
77
|
add_system_attribute : Add an OTDS System Attribute
|
|
72
78
|
|
|
73
79
|
get_trusted_sites : Get OTDS Trusted Sites
|
|
@@ -106,6 +112,9 @@ import logging
|
|
|
106
112
|
import json
|
|
107
113
|
import urllib.parse
|
|
108
114
|
import base64
|
|
115
|
+
import time
|
|
116
|
+
|
|
117
|
+
from http import HTTPStatus
|
|
109
118
|
import requests
|
|
110
119
|
|
|
111
120
|
logger = logging.getLogger("pyxecm.otds")
|
|
@@ -121,6 +130,8 @@ REQUEST_FORM_HEADERS = {
|
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
REQUEST_TIMEOUT = 60
|
|
133
|
+
REQUEST_RETRY_DELAY = 20
|
|
134
|
+
REQUEST_MAX_RETRIES = 2
|
|
124
135
|
|
|
125
136
|
|
|
126
137
|
class OTDS:
|
|
@@ -138,6 +149,7 @@ class OTDS:
|
|
|
138
149
|
username: str | None = None,
|
|
139
150
|
password: str | None = None,
|
|
140
151
|
otds_ticket: str | None = None,
|
|
152
|
+
bindPassword:str | None = None,
|
|
141
153
|
):
|
|
142
154
|
"""Initialize the OTDS object
|
|
143
155
|
|
|
@@ -177,6 +189,11 @@ class OTDS:
|
|
|
177
189
|
otds_config["password"] = password
|
|
178
190
|
else:
|
|
179
191
|
otds_config["password"] = ""
|
|
192
|
+
|
|
193
|
+
if bindPassword:
|
|
194
|
+
otds_config["bindPassword"] = bindPassword
|
|
195
|
+
else:
|
|
196
|
+
otds_config["bindPassword"] = ""
|
|
180
197
|
|
|
181
198
|
if otds_ticket:
|
|
182
199
|
self._cookie = {"OTDSTicket": otds_ticket}
|
|
@@ -191,6 +208,7 @@ class OTDS:
|
|
|
191
208
|
otds_config["restUrl"] = otdsRestUrl
|
|
192
209
|
|
|
193
210
|
otds_config["partitionUrl"] = otdsRestUrl + "/partitions"
|
|
211
|
+
otds_config["identityproviderprofiles"] = otdsRestUrl + "/identityproviderprofiles"
|
|
194
212
|
otds_config["accessRoleUrl"] = otdsRestUrl + "/accessroles"
|
|
195
213
|
otds_config["credentialUrl"] = otdsRestUrl + "/authentication/credentials"
|
|
196
214
|
otds_config["oauthClientUrl"] = otdsRestUrl + "/oauthclients"
|
|
@@ -213,6 +231,8 @@ class OTDS:
|
|
|
213
231
|
"""
|
|
214
232
|
return self._config
|
|
215
233
|
|
|
234
|
+
# end method definition
|
|
235
|
+
|
|
216
236
|
def cookie(self) -> dict:
|
|
217
237
|
"""Returns the login cookie of OTDS.
|
|
218
238
|
This is set by the authenticate() method
|
|
@@ -222,6 +242,8 @@ class OTDS:
|
|
|
222
242
|
"""
|
|
223
243
|
return self._cookie
|
|
224
244
|
|
|
245
|
+
# end method definition
|
|
246
|
+
|
|
225
247
|
def credentials(self) -> dict:
|
|
226
248
|
"""Returns the credentials (username + password)
|
|
227
249
|
|
|
@@ -233,6 +255,8 @@ class OTDS:
|
|
|
233
255
|
"password": self.config()["password"],
|
|
234
256
|
}
|
|
235
257
|
|
|
258
|
+
# end method definition
|
|
259
|
+
|
|
236
260
|
def base_url(self) -> str:
|
|
237
261
|
"""Returns the base URL of OTDS
|
|
238
262
|
|
|
@@ -241,6 +265,8 @@ class OTDS:
|
|
|
241
265
|
"""
|
|
242
266
|
return self.config()["baseUrl"]
|
|
243
267
|
|
|
268
|
+
# end method definition
|
|
269
|
+
|
|
244
270
|
def rest_url(self) -> str:
|
|
245
271
|
"""Returns the REST URL of OTDS
|
|
246
272
|
|
|
@@ -249,6 +275,8 @@ class OTDS:
|
|
|
249
275
|
"""
|
|
250
276
|
return self.config()["restUrl"]
|
|
251
277
|
|
|
278
|
+
# end method definition
|
|
279
|
+
|
|
252
280
|
def credential_url(self) -> str:
|
|
253
281
|
"""Returns the Credentials URL of OTDS
|
|
254
282
|
|
|
@@ -257,6 +285,8 @@ class OTDS:
|
|
|
257
285
|
"""
|
|
258
286
|
return self.config()["credentialUrl"]
|
|
259
287
|
|
|
288
|
+
# end method definition
|
|
289
|
+
|
|
260
290
|
def auth_handler_url(self) -> str:
|
|
261
291
|
"""Returns the Auth Handler URL of OTDS
|
|
262
292
|
|
|
@@ -265,6 +295,16 @@ class OTDS:
|
|
|
265
295
|
"""
|
|
266
296
|
return self.config()["authHandlerUrl"]
|
|
267
297
|
|
|
298
|
+
# end method definition
|
|
299
|
+
def synchronized_partition_url(self) -> str:
|
|
300
|
+
"""Returns the Partition URL of OTDS
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
str: synchronized partition url
|
|
304
|
+
"""
|
|
305
|
+
return self.config()["identityproviderprofiles"]
|
|
306
|
+
# end of method definition
|
|
307
|
+
|
|
268
308
|
def partition_url(self) -> str:
|
|
269
309
|
"""Returns the Partition URL of OTDS
|
|
270
310
|
|
|
@@ -273,6 +313,8 @@ class OTDS:
|
|
|
273
313
|
"""
|
|
274
314
|
return self.config()["partitionUrl"]
|
|
275
315
|
|
|
316
|
+
# end method definition
|
|
317
|
+
|
|
276
318
|
def access_role_url(self) -> str:
|
|
277
319
|
"""Returns the Access Role URL of OTDS
|
|
278
320
|
|
|
@@ -281,6 +323,8 @@ class OTDS:
|
|
|
281
323
|
"""
|
|
282
324
|
return self.config()["accessRoleUrl"]
|
|
283
325
|
|
|
326
|
+
# end method definition
|
|
327
|
+
|
|
284
328
|
def oauth_client_url(self) -> str:
|
|
285
329
|
"""Returns the OAuth Client URL of OTDS
|
|
286
330
|
|
|
@@ -289,6 +333,8 @@ class OTDS:
|
|
|
289
333
|
"""
|
|
290
334
|
return self.config()["oauthClientUrl"]
|
|
291
335
|
|
|
336
|
+
# end method definition
|
|
337
|
+
|
|
292
338
|
def resource_url(self) -> str:
|
|
293
339
|
"""Returns the Resource URL of OTDS
|
|
294
340
|
|
|
@@ -297,6 +343,8 @@ class OTDS:
|
|
|
297
343
|
"""
|
|
298
344
|
return self.config()["resourceUrl"]
|
|
299
345
|
|
|
346
|
+
# end method definition
|
|
347
|
+
|
|
300
348
|
def license_url(self) -> str:
|
|
301
349
|
"""Returns the License URL of OTDS
|
|
302
350
|
|
|
@@ -305,6 +353,8 @@ class OTDS:
|
|
|
305
353
|
"""
|
|
306
354
|
return self.config()["licenseUrl"]
|
|
307
355
|
|
|
356
|
+
# end method definition
|
|
357
|
+
|
|
308
358
|
def token_url(self) -> str:
|
|
309
359
|
"""Returns the Token URL of OTDS
|
|
310
360
|
|
|
@@ -313,6 +363,8 @@ class OTDS:
|
|
|
313
363
|
"""
|
|
314
364
|
return self.config()["tokenUrl"]
|
|
315
365
|
|
|
366
|
+
# end method definition
|
|
367
|
+
|
|
316
368
|
def users_url(self) -> str:
|
|
317
369
|
"""Returns the Users URL of OTDS
|
|
318
370
|
|
|
@@ -321,6 +373,8 @@ class OTDS:
|
|
|
321
373
|
"""
|
|
322
374
|
return self.config()["usersUrl"]
|
|
323
375
|
|
|
376
|
+
# end method definition
|
|
377
|
+
|
|
324
378
|
def groups_url(self) -> str:
|
|
325
379
|
"""Returns the Groups URL of OTDS
|
|
326
380
|
|
|
@@ -329,6 +383,8 @@ class OTDS:
|
|
|
329
383
|
"""
|
|
330
384
|
return self.config()["groupsUrl"]
|
|
331
385
|
|
|
386
|
+
# end method definition
|
|
387
|
+
|
|
332
388
|
def system_config_url(self) -> str:
|
|
333
389
|
"""Returns the System Config URL of OTDS
|
|
334
390
|
|
|
@@ -337,6 +393,8 @@ class OTDS:
|
|
|
337
393
|
"""
|
|
338
394
|
return self.config()["systemConfigUrl"]
|
|
339
395
|
|
|
396
|
+
# end method definition
|
|
397
|
+
|
|
340
398
|
def consolidation_url(self) -> str:
|
|
341
399
|
"""Returns the Consolidation URL of OTDS
|
|
342
400
|
|
|
@@ -345,6 +403,166 @@ class OTDS:
|
|
|
345
403
|
"""
|
|
346
404
|
return self.config()["consolidationUrl"]
|
|
347
405
|
|
|
406
|
+
# end method definition
|
|
407
|
+
|
|
408
|
+
def do_request(
|
|
409
|
+
self,
|
|
410
|
+
url: str,
|
|
411
|
+
method: str = "GET",
|
|
412
|
+
headers: dict | None = None,
|
|
413
|
+
data: dict | None = None,
|
|
414
|
+
json_data: dict | None = None,
|
|
415
|
+
files: dict | None = None,
|
|
416
|
+
timeout: int | None = REQUEST_TIMEOUT,
|
|
417
|
+
show_error: bool = True,
|
|
418
|
+
show_warning: bool = False,
|
|
419
|
+
warning_message: str = "",
|
|
420
|
+
failure_message: str = "",
|
|
421
|
+
success_message: str = "",
|
|
422
|
+
max_retries: int = REQUEST_MAX_RETRIES,
|
|
423
|
+
retry_forever: bool = False,
|
|
424
|
+
parse_request_response: bool = True,
|
|
425
|
+
) -> dict | None:
|
|
426
|
+
"""Call an OTDS REST API in a safe way
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
url (str): URL to send the request to.
|
|
430
|
+
method (str, optional): HTTP method (GET, POST, etc.). Defaults to "GET".
|
|
431
|
+
headers (dict | None, optional): Request Headers. Defaults to None.
|
|
432
|
+
data (dict | None, optional): Request payload. Defaults to None
|
|
433
|
+
files (dict | None, optional): Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
434
|
+
file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
435
|
+
timeout (int | None, optional): Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
436
|
+
show_error (bool, optional): Whether or not an error should be logged in case of a failed REST call.
|
|
437
|
+
If False, then only a warning is logged. Defaults to True.
|
|
438
|
+
warning_message (str, optional): Specific warning message. Defaults to "". If not given the error_message will be used.
|
|
439
|
+
failure_message (str, optional): Specific error message. Defaults to "".
|
|
440
|
+
success_message (str, optional): Specific success message. Defaults to "".
|
|
441
|
+
max_retries (int, optional): How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
|
|
442
|
+
retry_forever (bool, optional): Eventually wait forever - without timeout. Defaults to False.
|
|
443
|
+
parse_request_response (bool, optional): should the response.text be interpreted as json and loaded into a dictionary. True is the default.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
dict | None: Response of OTDS REST API or None in case of an error.
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
if headers is None:
|
|
450
|
+
headers = REQUEST_HEADERS
|
|
451
|
+
|
|
452
|
+
# In case of an expired session we reauthenticate and
|
|
453
|
+
# try 1 more time. Session expiration should not happen
|
|
454
|
+
# twice in a row:
|
|
455
|
+
retries = 0
|
|
456
|
+
|
|
457
|
+
while True:
|
|
458
|
+
try:
|
|
459
|
+
response = requests.request(
|
|
460
|
+
method=method,
|
|
461
|
+
url=url,
|
|
462
|
+
data=data,
|
|
463
|
+
json=json_data,
|
|
464
|
+
files=files,
|
|
465
|
+
headers=headers,
|
|
466
|
+
cookies=self.cookie(),
|
|
467
|
+
timeout=timeout,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
if response.ok:
|
|
471
|
+
if success_message:
|
|
472
|
+
logger.info(success_message)
|
|
473
|
+
if parse_request_response:
|
|
474
|
+
return self.parse_request_response(response)
|
|
475
|
+
else:
|
|
476
|
+
return response
|
|
477
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
478
|
+
elif response.status_code == 401 and retries == 0:
|
|
479
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
480
|
+
self.authenticate(revalidate=True)
|
|
481
|
+
retries += 1
|
|
482
|
+
else:
|
|
483
|
+
# Handle plain HTML responses to not pollute the logs
|
|
484
|
+
content_type = response.headers.get("content-type", None)
|
|
485
|
+
if content_type == "text/html":
|
|
486
|
+
response_text = "HTML content (only printed in debug log)"
|
|
487
|
+
else:
|
|
488
|
+
response_text = response.text
|
|
489
|
+
|
|
490
|
+
if show_error:
|
|
491
|
+
logger.error(
|
|
492
|
+
"%s; status -> %s/%s; error -> %s",
|
|
493
|
+
failure_message,
|
|
494
|
+
response.status_code,
|
|
495
|
+
HTTPStatus(response.status_code).phrase,
|
|
496
|
+
response_text,
|
|
497
|
+
)
|
|
498
|
+
elif show_warning:
|
|
499
|
+
logger.warning(
|
|
500
|
+
"%s; status -> %s/%s; warning -> %s",
|
|
501
|
+
warning_message if warning_message else failure_message,
|
|
502
|
+
response.status_code,
|
|
503
|
+
HTTPStatus(response.status_code).phrase,
|
|
504
|
+
response_text,
|
|
505
|
+
)
|
|
506
|
+
if content_type == "text/html":
|
|
507
|
+
logger.debug(
|
|
508
|
+
"%s; status -> %s/%s; warning -> %s",
|
|
509
|
+
failure_message,
|
|
510
|
+
response.status_code,
|
|
511
|
+
HTTPStatus(response.status_code).phrase,
|
|
512
|
+
response.text,
|
|
513
|
+
)
|
|
514
|
+
return None
|
|
515
|
+
except requests.exceptions.Timeout:
|
|
516
|
+
if retries <= max_retries:
|
|
517
|
+
logger.warning(
|
|
518
|
+
"Request timed out. Retrying in %s seconds...",
|
|
519
|
+
str(REQUEST_RETRY_DELAY),
|
|
520
|
+
)
|
|
521
|
+
retries += 1
|
|
522
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
523
|
+
else:
|
|
524
|
+
logger.error(
|
|
525
|
+
"%s; timeout error",
|
|
526
|
+
failure_message,
|
|
527
|
+
)
|
|
528
|
+
if retry_forever:
|
|
529
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
530
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
531
|
+
timeout = None
|
|
532
|
+
else:
|
|
533
|
+
return None
|
|
534
|
+
except requests.exceptions.ConnectionError:
|
|
535
|
+
if retries <= max_retries:
|
|
536
|
+
logger.warning(
|
|
537
|
+
"Connection error. Retrying in %s seconds...",
|
|
538
|
+
str(REQUEST_RETRY_DELAY),
|
|
539
|
+
)
|
|
540
|
+
retries += 1
|
|
541
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
542
|
+
else:
|
|
543
|
+
logger.error(
|
|
544
|
+
"%s; connection error",
|
|
545
|
+
failure_message,
|
|
546
|
+
)
|
|
547
|
+
if retry_forever:
|
|
548
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
549
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
550
|
+
timeout = None
|
|
551
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
552
|
+
else:
|
|
553
|
+
return None
|
|
554
|
+
# end try
|
|
555
|
+
logger.debug(
|
|
556
|
+
"Retrying REST API %s call -> %s... (retry = %s, cookie -> %s)",
|
|
557
|
+
method,
|
|
558
|
+
url,
|
|
559
|
+
str(retries),
|
|
560
|
+
str(self.cookie()),
|
|
561
|
+
)
|
|
562
|
+
# end while True
|
|
563
|
+
|
|
564
|
+
# end method definition
|
|
565
|
+
|
|
348
566
|
def parse_request_response(
|
|
349
567
|
self,
|
|
350
568
|
response_object: object,
|
|
@@ -365,6 +583,10 @@ class OTDS:
|
|
|
365
583
|
if not response_object:
|
|
366
584
|
return None
|
|
367
585
|
|
|
586
|
+
if not response_object.text:
|
|
587
|
+
logger.warning("Response text is empty. Cannot decode response.")
|
|
588
|
+
return None
|
|
589
|
+
|
|
368
590
|
try:
|
|
369
591
|
dict_object = json.loads(response_object.text)
|
|
370
592
|
except json.JSONDecodeError as e:
|
|
@@ -396,7 +618,7 @@ class OTDS:
|
|
|
396
618
|
|
|
397
619
|
# Already authenticated and session still valid?
|
|
398
620
|
if self._cookie and not revalidate:
|
|
399
|
-
logger.
|
|
621
|
+
logger.debug(
|
|
400
622
|
"Session still valid - return existing cookie -> %s",
|
|
401
623
|
str(self._cookie),
|
|
402
624
|
)
|
|
@@ -404,7 +626,7 @@ class OTDS:
|
|
|
404
626
|
|
|
405
627
|
otds_ticket = "NotSet"
|
|
406
628
|
|
|
407
|
-
logger.
|
|
629
|
+
logger.debug("Requesting OTDS ticket from -> %s", self.credential_url())
|
|
408
630
|
|
|
409
631
|
response = None
|
|
410
632
|
try:
|
|
@@ -429,7 +651,7 @@ class OTDS:
|
|
|
429
651
|
return None
|
|
430
652
|
else:
|
|
431
653
|
otds_ticket = authenticate_dict["ticket"]
|
|
432
|
-
logger.
|
|
654
|
+
logger.debug("Ticket -> %s", otds_ticket)
|
|
433
655
|
else:
|
|
434
656
|
logger.error("Failed to request an OTDS ticket; error -> %s", response.text)
|
|
435
657
|
return None
|
|
@@ -442,1813 +664,1563 @@ class OTDS:
|
|
|
442
664
|
|
|
443
665
|
# end method definition
|
|
444
666
|
|
|
445
|
-
def
|
|
446
|
-
|
|
447
|
-
path_to_license_file: str,
|
|
448
|
-
product_name: str,
|
|
449
|
-
product_description: str,
|
|
450
|
-
resource_id: str,
|
|
451
|
-
update: bool = True,
|
|
452
|
-
) -> dict | None:
|
|
453
|
-
"""Add a product license to an OTDS resource.
|
|
667
|
+
def add_partition(self, name: str, description: str) -> dict | None:
|
|
668
|
+
"""Add a new user partition to OTDS
|
|
454
669
|
|
|
455
670
|
Args:
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
product_description (str): product description
|
|
459
|
-
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
460
|
-
update (bool, optional): whether or not an existing license should be updated (default = True)
|
|
671
|
+
name (str): name of the new partition
|
|
672
|
+
description (str): description of the new partition
|
|
461
673
|
Returns:
|
|
462
|
-
dict: Request response
|
|
674
|
+
dict: Request response or None if the creation fails.
|
|
463
675
|
"""
|
|
464
676
|
|
|
465
|
-
|
|
466
|
-
try:
|
|
467
|
-
with open(path_to_license_file, "rt", encoding="UTF-8") as license_file:
|
|
468
|
-
license_content = license_file.read()
|
|
469
|
-
except IOError as exception:
|
|
470
|
-
logger.error(
|
|
471
|
-
"Error opening license file -> %s; error -> %s",
|
|
472
|
-
path_to_license_file,
|
|
473
|
-
exception.strerror,
|
|
474
|
-
)
|
|
475
|
-
return None
|
|
476
|
-
|
|
477
|
-
licensePostBodyJson = {
|
|
478
|
-
"description": product_description,
|
|
479
|
-
"name": product_name,
|
|
480
|
-
"values": [
|
|
481
|
-
{"name": "oTLicenseFile", "values": license_content},
|
|
482
|
-
{"name": "oTLicenseResource", "values": resource_id},
|
|
483
|
-
{"name": "oTLicenseFingerprintGenerator", "values": [None]},
|
|
484
|
-
],
|
|
485
|
-
}
|
|
677
|
+
partition_post_body_json = {"name": name, "description": description}
|
|
486
678
|
|
|
487
|
-
request_url = self.
|
|
488
|
-
# Check if we want to update an existing license:
|
|
489
|
-
if update:
|
|
490
|
-
existing_license = self.get_license_for_resource(resource_id)
|
|
491
|
-
if existing_license:
|
|
492
|
-
request_url += "/" + existing_license[0]["id"]
|
|
493
|
-
else:
|
|
494
|
-
logger.info(
|
|
495
|
-
"No existing license for resource -> %s found - adding a new license...",
|
|
496
|
-
resource_id,
|
|
497
|
-
)
|
|
498
|
-
# change strategy to create a new license:
|
|
499
|
-
update = False
|
|
679
|
+
request_url = self.partition_url()
|
|
500
680
|
|
|
501
|
-
logger.
|
|
502
|
-
"Adding
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
resource_id,
|
|
681
|
+
logger.debug(
|
|
682
|
+
"Adding user partition -> '%s' (%s); calling -> %s",
|
|
683
|
+
name,
|
|
684
|
+
description,
|
|
506
685
|
request_url,
|
|
507
686
|
)
|
|
508
687
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
headers=REQUEST_HEADERS,
|
|
517
|
-
cookies=self.cookie(),
|
|
518
|
-
timeout=None,
|
|
519
|
-
)
|
|
520
|
-
else:
|
|
521
|
-
# Do a REST POST call for creation of a new license:
|
|
522
|
-
response = requests.post(
|
|
523
|
-
url=request_url,
|
|
524
|
-
json=licensePostBodyJson,
|
|
525
|
-
headers=REQUEST_HEADERS,
|
|
526
|
-
cookies=self.cookie(),
|
|
527
|
-
timeout=None,
|
|
528
|
-
)
|
|
529
|
-
if response.ok:
|
|
530
|
-
return self.parse_request_response(response)
|
|
531
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
532
|
-
elif response.status_code == 401 and retries == 0:
|
|
533
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
534
|
-
self.authenticate(revalidate=True)
|
|
535
|
-
retries += 1
|
|
536
|
-
else:
|
|
537
|
-
logger.error(
|
|
538
|
-
"Failed to add product license -> %s for product -> %s; error -> %s (%s)",
|
|
539
|
-
path_to_license_file,
|
|
540
|
-
product_description,
|
|
541
|
-
response.text,
|
|
542
|
-
response.status_code,
|
|
543
|
-
)
|
|
544
|
-
return None
|
|
688
|
+
return self.do_request(
|
|
689
|
+
url=request_url,
|
|
690
|
+
method="POST",
|
|
691
|
+
json_data=partition_post_body_json,
|
|
692
|
+
timeout=None,
|
|
693
|
+
failure_message="Failed to add user partition -> '{}'".format(name),
|
|
694
|
+
)
|
|
545
695
|
|
|
546
696
|
# end method definition
|
|
547
697
|
|
|
548
|
-
def
|
|
549
|
-
"""Get
|
|
698
|
+
def get_partition(self, name: str, show_error: bool = True) -> dict | None:
|
|
699
|
+
"""Get an existing user partition from OTDS
|
|
550
700
|
|
|
551
701
|
Args:
|
|
552
|
-
|
|
702
|
+
name (str): name of the partition to retrieve
|
|
703
|
+
show_error (bool, optional): whether or not we want to log an error
|
|
704
|
+
if partion is not found
|
|
553
705
|
Returns:
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
licenses have this format:
|
|
557
|
-
{
|
|
558
|
-
'_oTLicenseType': 'NON-PRODUCTION',
|
|
559
|
-
'_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
560
|
-
'_oTLicenseResourceName': 'cs',
|
|
561
|
-
'_oTLicenseProduct': 'EXTENDED_ECM',
|
|
562
|
-
'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
563
|
-
'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
564
|
-
'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
565
|
-
'description': 'CS license',
|
|
566
|
-
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
567
|
-
}
|
|
706
|
+
dict: Request response or None if the REST call fails.
|
|
568
707
|
"""
|
|
569
708
|
|
|
570
|
-
request_url = (
|
|
571
|
-
self.license_url()
|
|
572
|
-
+ "/assignedlicenses?resourceID="
|
|
573
|
-
+ resource_id
|
|
574
|
-
+ "&validOnly=false"
|
|
575
|
-
)
|
|
709
|
+
request_url = "{}/{}".format(self.config()["partitionUrl"], name)
|
|
576
710
|
|
|
577
|
-
logger.
|
|
578
|
-
"Get license for resource -> %s; calling -> %s", resource_id, request_url
|
|
579
|
-
)
|
|
711
|
+
logger.debug("Get user partition -> '%s'; calling -> %s", name, request_url)
|
|
580
712
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
)
|
|
589
|
-
if response.ok:
|
|
590
|
-
response_dict = self.parse_request_response(response)
|
|
591
|
-
if not response_dict:
|
|
592
|
-
return None
|
|
593
|
-
return response_dict["licenseObjects"]["_licenses"]
|
|
594
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
595
|
-
elif response.status_code == 401 and retries == 0:
|
|
596
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
597
|
-
self.authenticate(revalidate=True)
|
|
598
|
-
retries += 1
|
|
599
|
-
else:
|
|
600
|
-
logger.error(
|
|
601
|
-
"Failed to get license for resource -> %s; error -> %s (%s)",
|
|
602
|
-
resource_id,
|
|
603
|
-
response.text,
|
|
604
|
-
response.status_code,
|
|
605
|
-
)
|
|
606
|
-
return None
|
|
713
|
+
return self.do_request(
|
|
714
|
+
url=request_url,
|
|
715
|
+
method="GET",
|
|
716
|
+
timeout=None,
|
|
717
|
+
failure_message="Failed to get user partition -> '{}'".format(name),
|
|
718
|
+
show_error=show_error,
|
|
719
|
+
)
|
|
607
720
|
|
|
608
721
|
# end method definition
|
|
609
722
|
|
|
610
|
-
def
|
|
611
|
-
|
|
723
|
+
def add_user(
|
|
724
|
+
self,
|
|
725
|
+
partition: str,
|
|
726
|
+
name: str,
|
|
727
|
+
description: str = "",
|
|
728
|
+
first_name: str = "",
|
|
729
|
+
last_name: str = "",
|
|
730
|
+
email: str = "",
|
|
731
|
+
) -> dict | None:
|
|
732
|
+
"""Add a new user to a user partition in OTDS
|
|
612
733
|
|
|
613
734
|
Args:
|
|
614
|
-
|
|
615
|
-
|
|
735
|
+
partition (str): name of the OTDS user partition (needs to exist)
|
|
736
|
+
name (str): login name of the new user
|
|
737
|
+
description (str, optional): description of the new user
|
|
738
|
+
first_name (str, optional): first name of the new user
|
|
739
|
+
last_name (str, optional): last name of the new user
|
|
740
|
+
email (str, optional): email address of the new user
|
|
616
741
|
Returns:
|
|
617
|
-
|
|
742
|
+
dict: Request response or None if the creation fails.
|
|
618
743
|
"""
|
|
619
744
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
745
|
+
user_post_body_json = {
|
|
746
|
+
"userPartitionID": partition,
|
|
747
|
+
"values": [
|
|
748
|
+
{"name": "sn", "values": [last_name]},
|
|
749
|
+
{"name": "givenName", "values": [first_name]},
|
|
750
|
+
{"name": "mail", "values": [email]},
|
|
751
|
+
],
|
|
752
|
+
"name": name,
|
|
753
|
+
"description": description,
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
request_url = self.users_url()
|
|
757
|
+
|
|
758
|
+
logger.debug(
|
|
759
|
+
"Adding user -> '%s' to partition -> '%s'; calling -> %s",
|
|
760
|
+
name,
|
|
761
|
+
partition,
|
|
626
762
|
request_url,
|
|
627
763
|
)
|
|
764
|
+
logger.debug("User Attributes -> %s", str(user_post_body_json))
|
|
628
765
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
)
|
|
637
|
-
if response.ok:
|
|
638
|
-
return True
|
|
639
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
640
|
-
elif response.status_code == 401 and retries == 0:
|
|
641
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
642
|
-
self.authenticate(revalidate=True)
|
|
643
|
-
retries += 1
|
|
644
|
-
else:
|
|
645
|
-
logger.error(
|
|
646
|
-
"Failed to delete license -> %s for resource -> %s; error -> %s (%s)",
|
|
647
|
-
license_id,
|
|
648
|
-
resource_id,
|
|
649
|
-
response.text,
|
|
650
|
-
response.status_code,
|
|
651
|
-
)
|
|
652
|
-
return False
|
|
766
|
+
return self.do_request(
|
|
767
|
+
url=request_url,
|
|
768
|
+
method="POST",
|
|
769
|
+
json_data=user_post_body_json,
|
|
770
|
+
timeout=None,
|
|
771
|
+
failure_message="Failed to add user -> '{}'".format(name),
|
|
772
|
+
)
|
|
653
773
|
|
|
654
774
|
# end method definition
|
|
655
775
|
|
|
656
|
-
def
|
|
657
|
-
|
|
658
|
-
partition: str,
|
|
659
|
-
user_id: str,
|
|
660
|
-
resource_id: str,
|
|
661
|
-
license_feature: str,
|
|
662
|
-
license_name: str,
|
|
663
|
-
license_type: str = "Full",
|
|
664
|
-
) -> bool:
|
|
665
|
-
"""Assign an OTDS user to a product license (feature) in OTDS.
|
|
776
|
+
def get_user(self, partition: str, user_id: str) -> dict | None:
|
|
777
|
+
"""Get a user by its partition and user ID
|
|
666
778
|
|
|
667
779
|
Args:
|
|
668
|
-
partition (str):
|
|
780
|
+
partition (str): name of the partition
|
|
669
781
|
user_id (str): ID of the user (= login name)
|
|
670
|
-
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
671
|
-
license_feature (str): name of the license feature
|
|
672
|
-
license_name (str): name of the license to assign
|
|
673
|
-
license_type (str, optional): deault is "Full", Extended ECM also has "Occasional"
|
|
674
782
|
Returns:
|
|
675
|
-
|
|
783
|
+
dict: Request response or None if the user was not found.
|
|
676
784
|
"""
|
|
677
785
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
for lic in licenses:
|
|
681
|
-
if lic["_oTLicenseProduct"] == license_name:
|
|
682
|
-
license_location = lic["id"]
|
|
683
|
-
|
|
684
|
-
try:
|
|
685
|
-
license_location
|
|
686
|
-
except UnboundLocalError:
|
|
687
|
-
logger.error(
|
|
688
|
-
"Cannot find license -> %s for resource -> %s",
|
|
689
|
-
license_name,
|
|
690
|
-
resource_id,
|
|
691
|
-
)
|
|
692
|
-
return False
|
|
693
|
-
|
|
694
|
-
user = self.get_user(partition, user_id)
|
|
695
|
-
if user:
|
|
696
|
-
user_location = user["location"]
|
|
697
|
-
else:
|
|
698
|
-
logger.error("Cannot find location for user -> %s", user_id)
|
|
699
|
-
return False
|
|
700
|
-
|
|
701
|
-
licensePostBodyJson = {
|
|
702
|
-
"_oTLicenseType": license_type,
|
|
703
|
-
"_oTLicenseProduct": "users",
|
|
704
|
-
"name": user_location,
|
|
705
|
-
"values": [{"name": "counter", "values": [license_feature]}],
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
request_url = self.license_url() + "/object/" + license_location
|
|
786
|
+
request_url = self.users_url() + "/" + user_id + "@" + partition
|
|
709
787
|
|
|
710
|
-
logger.
|
|
711
|
-
"
|
|
712
|
-
license_feature,
|
|
713
|
-
license_location,
|
|
714
|
-
resource_id,
|
|
788
|
+
logger.debug(
|
|
789
|
+
"Get user -> '%s' in partition -> '%s'; calling -> %s",
|
|
715
790
|
user_id,
|
|
791
|
+
partition,
|
|
716
792
|
request_url,
|
|
717
793
|
)
|
|
718
794
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
cookies=self.cookie(),
|
|
726
|
-
timeout=None,
|
|
727
|
-
)
|
|
728
|
-
if response.ok:
|
|
729
|
-
logger.info(
|
|
730
|
-
"Added license feature -> %s for user -> %s",
|
|
731
|
-
license_feature,
|
|
732
|
-
user_id,
|
|
733
|
-
)
|
|
734
|
-
return True
|
|
735
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
736
|
-
elif response.status_code == 401 and retries == 0:
|
|
737
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
738
|
-
self.authenticate(revalidate=True)
|
|
739
|
-
retries += 1
|
|
740
|
-
else:
|
|
741
|
-
logger.error(
|
|
742
|
-
"Failed to add license feature -> %s associated with resource -> %s to user -> %s; error -> %s (%s)",
|
|
743
|
-
license_feature,
|
|
744
|
-
resource_id,
|
|
745
|
-
user_id,
|
|
746
|
-
response.text,
|
|
747
|
-
response.status_code,
|
|
748
|
-
)
|
|
749
|
-
return False
|
|
795
|
+
return self.do_request(
|
|
796
|
+
url=request_url,
|
|
797
|
+
method="GET",
|
|
798
|
+
timeout=None,
|
|
799
|
+
failure_message="Failed to get user -> '{}'".format(user_id),
|
|
800
|
+
)
|
|
750
801
|
|
|
751
802
|
# end method definition
|
|
752
803
|
|
|
753
|
-
def
|
|
754
|
-
|
|
755
|
-
partition_name: str,
|
|
756
|
-
resource_id: str,
|
|
757
|
-
license_feature: str,
|
|
758
|
-
license_name: str,
|
|
759
|
-
license_type: str = "Full",
|
|
760
|
-
) -> bool:
|
|
761
|
-
"""Assign an OTDS partition to a product license (feature).
|
|
804
|
+
def get_users(self, partition: str = "", limit: int | None = None) -> dict | None:
|
|
805
|
+
"""Get all users in a partition partition
|
|
762
806
|
|
|
763
807
|
Args:
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
767
|
-
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
768
|
-
license_type (str, optional): deault is "Full", Extended ECM also has "Occasional"
|
|
808
|
+
partition (str, optional): name of the partition
|
|
809
|
+
limit (int): maximum number of users to return
|
|
769
810
|
Returns:
|
|
770
|
-
|
|
811
|
+
dict: Request response or None if the user was not found.
|
|
771
812
|
"""
|
|
772
813
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
# licenses have this format:
|
|
781
|
-
# {
|
|
782
|
-
# '_oTLicenseType': 'NON-PRODUCTION',
|
|
783
|
-
# '_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
784
|
-
# '_oTLicenseResourceName': 'cs',
|
|
785
|
-
# '_oTLicenseProduct': 'EXTENDED_ECM',
|
|
786
|
-
# 'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
787
|
-
# 'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
788
|
-
# 'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
789
|
-
# 'description': 'CS license',
|
|
790
|
-
# 'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
791
|
-
# }
|
|
792
|
-
for lic in licenses:
|
|
793
|
-
if lic["_oTLicenseProduct"] == license_name:
|
|
794
|
-
license_location = lic["id"]
|
|
814
|
+
# Add query parameters (these are NOT passed via JSon body!)
|
|
815
|
+
query = {}
|
|
816
|
+
if limit:
|
|
817
|
+
query["limit"] = limit
|
|
818
|
+
if partition:
|
|
819
|
+
query["where_partition_name"] = partition
|
|
795
820
|
|
|
796
|
-
|
|
797
|
-
license_location
|
|
798
|
-
except UnboundLocalError:
|
|
799
|
-
logger.error(
|
|
800
|
-
"Cannot find license -> %s for resource -> %s",
|
|
801
|
-
license_name,
|
|
802
|
-
resource_id,
|
|
803
|
-
)
|
|
804
|
-
return False
|
|
821
|
+
encoded_query = urllib.parse.urlencode(query, doseq=True)
|
|
805
822
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
"name": partition_name,
|
|
810
|
-
"values": [{"name": "counter", "values": [license_feature]}],
|
|
811
|
-
}
|
|
823
|
+
request_url = self.users_url()
|
|
824
|
+
if query:
|
|
825
|
+
request_url += "?{}".format(encoded_query)
|
|
812
826
|
|
|
813
|
-
|
|
827
|
+
if partition:
|
|
828
|
+
logger.debug(
|
|
829
|
+
"Get all users in partition -> '%s' (limit -> %s); calling -> %s",
|
|
830
|
+
partition,
|
|
831
|
+
limit,
|
|
832
|
+
request_url,
|
|
833
|
+
)
|
|
834
|
+
failure_message = "Failed to get all users in partition -> '{}'".format(
|
|
835
|
+
partition
|
|
836
|
+
)
|
|
837
|
+
else:
|
|
838
|
+
logger.debug(
|
|
839
|
+
"Get all users (limit -> %s); calling -> %s",
|
|
840
|
+
limit,
|
|
841
|
+
request_url,
|
|
842
|
+
)
|
|
843
|
+
failure_message = "Failed to get all users"
|
|
814
844
|
|
|
815
|
-
|
|
816
|
-
"
|
|
817
|
-
license_feature,
|
|
818
|
-
license_location,
|
|
819
|
-
resource_id,
|
|
820
|
-
partition_name,
|
|
821
|
-
request_url,
|
|
845
|
+
return self.do_request(
|
|
846
|
+
url=request_url, method="GET", timeout=None, failure_message=failure_message
|
|
822
847
|
)
|
|
823
848
|
|
|
824
|
-
retries = 0
|
|
825
|
-
while True:
|
|
826
|
-
response = requests.post(
|
|
827
|
-
url=request_url,
|
|
828
|
-
json=licensePostBodyJson,
|
|
829
|
-
headers=REQUEST_HEADERS,
|
|
830
|
-
cookies=self.cookie(),
|
|
831
|
-
timeout=None,
|
|
832
|
-
)
|
|
833
|
-
if response.ok:
|
|
834
|
-
logger.info(
|
|
835
|
-
"Added license feature -> %s for partition -> %s",
|
|
836
|
-
license_feature,
|
|
837
|
-
partition_name,
|
|
838
|
-
)
|
|
839
|
-
return True
|
|
840
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
841
|
-
elif response.status_code == 401 and retries == 0:
|
|
842
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
843
|
-
self.authenticate(revalidate=True)
|
|
844
|
-
retries += 1
|
|
845
|
-
else:
|
|
846
|
-
logger.error(
|
|
847
|
-
"Failed to add license feature -> %s associated with resource -> %s to partition -> %s; error -> %s (%s)",
|
|
848
|
-
license_feature,
|
|
849
|
-
resource_id,
|
|
850
|
-
partition_name,
|
|
851
|
-
response.text,
|
|
852
|
-
response.status_code,
|
|
853
|
-
)
|
|
854
|
-
return False
|
|
855
|
-
|
|
856
849
|
# end method definition
|
|
857
850
|
|
|
858
|
-
def
|
|
859
|
-
self,
|
|
860
|
-
resource_id: str,
|
|
861
|
-
license_feature: str,
|
|
862
|
-
license_name: str,
|
|
851
|
+
def update_user(
|
|
852
|
+
self, partition: str, user_id: str, attribute_name: str, attribute_value: str
|
|
863
853
|
) -> dict | None:
|
|
864
|
-
"""
|
|
865
|
-
associated with an OTDS resource (like "cs").
|
|
854
|
+
"""Update a user attribute with a new value
|
|
866
855
|
|
|
867
856
|
Args:
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
Example return value:
|
|
875
|
-
{
|
|
876
|
-
'status': 0,
|
|
877
|
-
'displayString': 'Success',
|
|
878
|
-
'exceptions': None,
|
|
879
|
-
'retValue': 0,
|
|
880
|
-
'listGroupsResults': {'groups': [...], 'actualPageSize': 0, 'nextPageCookie': None, 'requestedPageSize': 250},
|
|
881
|
-
'listUsersResults': {'users': [...], 'actualPageSize': 53, 'nextPageCookie': None, 'requestedPageSize': 250},
|
|
882
|
-
'listUserPartitionResult': {'_userPartitions': [...], 'warningMessage': None, 'actualPageSize': 0, 'nextPageCookie': None, 'requestedPageSize': 250},
|
|
883
|
-
'version': 1
|
|
884
|
-
}
|
|
857
|
+
partition (str): name of the partition
|
|
858
|
+
user_id (str): ID of the user (= login name)
|
|
859
|
+
attribute_name (str): name of the attribute
|
|
860
|
+
attribute_value (str): new value of the attribute
|
|
861
|
+
Return:
|
|
862
|
+
dict: Request response or None if the update fails.
|
|
885
863
|
"""
|
|
886
864
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
865
|
+
if attribute_name in ["description"]:
|
|
866
|
+
user_patch_body_json = {
|
|
867
|
+
"userPartitionID": partition,
|
|
868
|
+
attribute_name: attribute_value,
|
|
869
|
+
}
|
|
870
|
+
else:
|
|
871
|
+
user_patch_body_json = {
|
|
872
|
+
"userPartitionID": partition,
|
|
873
|
+
"values": [{"name": attribute_name, "values": [attribute_value]}],
|
|
874
|
+
}
|
|
893
875
|
|
|
894
|
-
|
|
895
|
-
# {
|
|
896
|
-
# '_oTLicenseType': 'NON-PRODUCTION',
|
|
897
|
-
# '_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
898
|
-
# '_oTLicenseResourceName': 'cs',
|
|
899
|
-
# '_oTLicenseProduct': 'EXTENDED_ECM',
|
|
900
|
-
# 'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
901
|
-
# 'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
902
|
-
# 'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
903
|
-
# 'description': 'CS license',
|
|
904
|
-
# 'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
905
|
-
# }
|
|
906
|
-
for lic in licenses:
|
|
907
|
-
if lic["_oTLicenseProduct"] == license_name:
|
|
908
|
-
license_location = lic["location"]
|
|
876
|
+
request_url = self.users_url() + "/" + user_id
|
|
909
877
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
)
|
|
918
|
-
return False
|
|
878
|
+
logger.debug(
|
|
879
|
+
"Update user -> '%s' attribute -> '%s' to value -> '%s'; calling -> %s",
|
|
880
|
+
user_id,
|
|
881
|
+
attribute_name,
|
|
882
|
+
attribute_value,
|
|
883
|
+
request_url,
|
|
884
|
+
)
|
|
919
885
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
886
|
+
return self.do_request(
|
|
887
|
+
url=request_url,
|
|
888
|
+
method="PATCH",
|
|
889
|
+
json_data=user_patch_body_json,
|
|
890
|
+
timeout=None,
|
|
891
|
+
failure_message="Failed to update user -> '{}'".format(user_id),
|
|
926
892
|
)
|
|
927
893
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
894
|
+
# end method definition
|
|
895
|
+
|
|
896
|
+
def delete_user(self, partition: str, user_id: str) -> bool:
|
|
897
|
+
"""Delete an existing user
|
|
898
|
+
|
|
899
|
+
Args:
|
|
900
|
+
partition (str): name of the partition
|
|
901
|
+
user_id (str): Id (= login name) of the user
|
|
902
|
+
Returns:
|
|
903
|
+
bool: True = success, False = error
|
|
904
|
+
"""
|
|
905
|
+
|
|
906
|
+
request_url = self.users_url() + "/" + user_id + "@" + partition
|
|
907
|
+
|
|
908
|
+
logger.debug(
|
|
909
|
+
"Delete user -> '%s' in partition -> '%s'; calling -> %s",
|
|
910
|
+
user_id,
|
|
911
|
+
partition,
|
|
933
912
|
request_url,
|
|
934
913
|
)
|
|
935
914
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
949
|
-
self.authenticate(revalidate=True)
|
|
950
|
-
retries += 1
|
|
951
|
-
else:
|
|
952
|
-
logger.error(
|
|
953
|
-
"Failed to get licensed objects for license -> %s and license feature -> %s associated with resource -> %s; error -> %s (%s)",
|
|
954
|
-
license_name,
|
|
955
|
-
license_feature,
|
|
956
|
-
resource_id,
|
|
957
|
-
response.text,
|
|
958
|
-
response.status_code,
|
|
959
|
-
)
|
|
960
|
-
return None
|
|
915
|
+
response = self.do_request(
|
|
916
|
+
url=request_url,
|
|
917
|
+
method="DELETE",
|
|
918
|
+
timeout=None,
|
|
919
|
+
failure_message="Failed to delete user -> '{}'".format(user_id),
|
|
920
|
+
parse_request_response=False,
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
if response and response.ok:
|
|
924
|
+
return True
|
|
925
|
+
|
|
926
|
+
return False
|
|
961
927
|
|
|
962
928
|
# end method definition
|
|
963
929
|
|
|
964
|
-
def
|
|
965
|
-
|
|
966
|
-
) -> bool:
|
|
967
|
-
"""Check if a user is licensed for a license and license feature associated with a particular OTDS resource.
|
|
930
|
+
def reset_user_password(self, user_id: str, password: str) -> bool:
|
|
931
|
+
"""Reset a password of an existing user
|
|
968
932
|
|
|
969
933
|
Args:
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
973
|
-
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
974
|
-
|
|
934
|
+
user_id (str): Id (= login name) of the user
|
|
935
|
+
password (str): new password of the user
|
|
975
936
|
Returns:
|
|
976
|
-
bool: True
|
|
937
|
+
bool: True = success, False = error.
|
|
977
938
|
"""
|
|
978
939
|
|
|
979
|
-
|
|
980
|
-
resource_id=resource_id,
|
|
981
|
-
license_feature=license_feature,
|
|
982
|
-
license_name=license_name,
|
|
983
|
-
)
|
|
984
|
-
|
|
985
|
-
if not response or not response["listUsersResults"]:
|
|
986
|
-
return False
|
|
940
|
+
user_post_body_json = {"newPassword": password}
|
|
987
941
|
|
|
988
|
-
|
|
942
|
+
request_url = "{}/{}/password".format(self.users_url(), user_id)
|
|
989
943
|
|
|
990
|
-
|
|
991
|
-
|
|
944
|
+
logger.debug(
|
|
945
|
+
"Resetting password for user -> '%s'; calling -> %s", user_id, request_url
|
|
946
|
+
)
|
|
992
947
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
948
|
+
response = self.do_request(
|
|
949
|
+
url=request_url,
|
|
950
|
+
method="PUT",
|
|
951
|
+
json_data=user_post_body_json,
|
|
952
|
+
timeout=None,
|
|
953
|
+
failure_message="Failed to reset password for user -> '{}'".format(user_id),
|
|
954
|
+
parse_request_response=False,
|
|
996
955
|
)
|
|
997
956
|
|
|
998
|
-
if
|
|
957
|
+
if response and response.ok:
|
|
999
958
|
return True
|
|
1000
959
|
|
|
1001
960
|
return False
|
|
1002
961
|
|
|
1003
962
|
# end method definition
|
|
1004
963
|
|
|
1005
|
-
def
|
|
1006
|
-
|
|
1007
|
-
) -> bool:
|
|
1008
|
-
"""Check if a group is licensed for a license and license feature associated with a particular OTDS resource.
|
|
964
|
+
def add_group(self, partition: str, name: str, description: str) -> dict | None:
|
|
965
|
+
"""Add a new user group to a user partition in OTDS
|
|
1009
966
|
|
|
1010
967
|
Args:
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
1015
|
-
|
|
968
|
+
partition (str): name of the OTDS user partition (needs to exist)
|
|
969
|
+
name (str): name of the new group
|
|
970
|
+
description (str): description of the new group
|
|
1016
971
|
Returns:
|
|
1017
|
-
|
|
972
|
+
dict: Request response (json) or None if the creation fails.
|
|
1018
973
|
"""
|
|
1019
974
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
975
|
+
group_post_body_json = {
|
|
976
|
+
"userPartitionID": partition,
|
|
977
|
+
"name": name,
|
|
978
|
+
"description": description,
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
request_url = self.groups_url()
|
|
982
|
+
|
|
983
|
+
logger.debug(
|
|
984
|
+
"Adding group -> '%s' to partition -> '%s'; calling -> %s",
|
|
985
|
+
name,
|
|
986
|
+
partition,
|
|
987
|
+
request_url,
|
|
1024
988
|
)
|
|
989
|
+
logger.debug("Group Attributes -> %s", str(group_post_body_json))
|
|
1025
990
|
|
|
1026
|
-
|
|
1027
|
-
|
|
991
|
+
return self.do_request(
|
|
992
|
+
url=request_url,
|
|
993
|
+
method="POST",
|
|
994
|
+
json_data=group_post_body_json,
|
|
995
|
+
timeout=None,
|
|
996
|
+
failure_message="Failed to reset password for user -> '{}'".format(name),
|
|
997
|
+
)
|
|
1028
998
|
|
|
1029
|
-
|
|
999
|
+
# end method definition
|
|
1030
1000
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1001
|
+
def get_group(self, group: str, show_error: bool = True) -> dict | None:
|
|
1002
|
+
"""Get a OTDS group by its group name
|
|
1033
1003
|
|
|
1034
|
-
|
|
1035
|
-
(
|
|
1036
|
-
|
|
1037
|
-
|
|
1004
|
+
Args:
|
|
1005
|
+
group (str): ID of the group (= group name)
|
|
1006
|
+
show_error (bool, optional): treat as error if resource is not found
|
|
1007
|
+
Return:
|
|
1008
|
+
dict: Request response or None if the group was not found.
|
|
1009
|
+
Example values:
|
|
1010
|
+
{
|
|
1011
|
+
'numMembers': 7,
|
|
1012
|
+
'userPartitionID': 'Content Server Members',
|
|
1013
|
+
'name': 'Sales',
|
|
1014
|
+
'location': 'oTGroup=3f921294-b92a-4c9e-bf7c-b50df16bb937,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net',
|
|
1015
|
+
'id': 'Sales@Content Server Members',
|
|
1016
|
+
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...],
|
|
1017
|
+
'description': None,
|
|
1018
|
+
'uuid': '3f921294-b92a-4c9e-bf7c-b50df16bb937',
|
|
1019
|
+
'objectClass': 'oTGroup',
|
|
1020
|
+
'customAttributes': None,
|
|
1021
|
+
'originUUID': None,
|
|
1022
|
+
'urlId': 'Sales@Content Server Members',
|
|
1023
|
+
'urlLocation': 'oTGroup=3f921294-b92a-4c9e-bf7c-b50df16bb937,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net'
|
|
1024
|
+
}
|
|
1025
|
+
"""
|
|
1038
1026
|
|
|
1039
|
-
|
|
1040
|
-
return True
|
|
1027
|
+
request_url = self.groups_url() + "/" + group
|
|
1041
1028
|
|
|
1042
|
-
|
|
1029
|
+
logger.debug("Get group -> '%s'; calling -> %s", group, request_url)
|
|
1030
|
+
|
|
1031
|
+
return self.do_request(
|
|
1032
|
+
url=request_url,
|
|
1033
|
+
method="GET",
|
|
1034
|
+
timeout=None,
|
|
1035
|
+
failure_message="Failed to get group -> '{}'".format(group),
|
|
1036
|
+
show_error=show_error,
|
|
1037
|
+
)
|
|
1043
1038
|
|
|
1044
1039
|
# end method definition
|
|
1045
1040
|
|
|
1046
|
-
def
|
|
1047
|
-
|
|
1048
|
-
partition_name: str,
|
|
1049
|
-
resource_id: str,
|
|
1050
|
-
license_feature: str,
|
|
1051
|
-
license_name: str,
|
|
1052
|
-
) -> bool:
|
|
1053
|
-
"""Check if a partition is licensed for a license and license feature associated with a particular OTDS resource.
|
|
1041
|
+
def add_user_to_group(self, user: str, group: str) -> bool:
|
|
1042
|
+
"""Add an existing user to an existing group in OTDS
|
|
1054
1043
|
|
|
1055
1044
|
Args:
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
1059
|
-
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
1060
|
-
|
|
1045
|
+
user (str): name of the OTDS user (needs to exist)
|
|
1046
|
+
group (str): name of the OTDS group (needs to exist)
|
|
1061
1047
|
Returns:
|
|
1062
|
-
bool: True if
|
|
1048
|
+
bool: True, if request is successful, False otherwise.
|
|
1063
1049
|
"""
|
|
1064
1050
|
|
|
1065
|
-
|
|
1066
|
-
resource_id=resource_id,
|
|
1067
|
-
license_feature=license_feature,
|
|
1068
|
-
license_name=license_name,
|
|
1069
|
-
)
|
|
1070
|
-
|
|
1071
|
-
if not response or not response["listUserPartitionResult"]:
|
|
1072
|
-
return False
|
|
1051
|
+
user_to_group_post_body_json = {"stringList": [group]}
|
|
1073
1052
|
|
|
1074
|
-
|
|
1053
|
+
request_url = self.users_url() + "/" + user + "/memberof"
|
|
1075
1054
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1055
|
+
logger.debug(
|
|
1056
|
+
"Adding user -> '%s' to group -> '%s'; calling -> %s",
|
|
1057
|
+
user,
|
|
1058
|
+
group,
|
|
1059
|
+
request_url,
|
|
1060
|
+
)
|
|
1078
1061
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1062
|
+
# OTDS delivers an empty response.text for the API, so we don't parse it here:
|
|
1063
|
+
response = self.do_request(
|
|
1064
|
+
url=request_url,
|
|
1065
|
+
method="POST",
|
|
1066
|
+
json_data=user_to_group_post_body_json,
|
|
1067
|
+
timeout=None,
|
|
1068
|
+
failure_message="Failed to add user -> '{}' to group -> '{}'".format(
|
|
1069
|
+
user, group
|
|
1070
|
+
),
|
|
1071
|
+
parse_request_response=False,
|
|
1082
1072
|
)
|
|
1083
1073
|
|
|
1084
|
-
if
|
|
1074
|
+
if response and response.ok:
|
|
1085
1075
|
return True
|
|
1086
1076
|
|
|
1087
1077
|
return False
|
|
1088
1078
|
|
|
1089
1079
|
# end method definition
|
|
1090
1080
|
|
|
1091
|
-
def
|
|
1092
|
-
"""Add
|
|
1081
|
+
def add_group_to_parent_group(self, group: str, parent_group: str) -> bool:
|
|
1082
|
+
"""Add an existing group to an existing parent group in OTDS
|
|
1093
1083
|
|
|
1094
1084
|
Args:
|
|
1095
|
-
|
|
1096
|
-
|
|
1085
|
+
group (str): name of the OTDS group (needs to exist)
|
|
1086
|
+
parent_group (str): name of the OTDS parent group (needs to exist)
|
|
1097
1087
|
Returns:
|
|
1098
|
-
|
|
1088
|
+
bool: True, if request is successful, False otherwise.
|
|
1099
1089
|
"""
|
|
1100
1090
|
|
|
1101
|
-
|
|
1091
|
+
group_to_parent_group_post_body_json = {"stringList": [parent_group]}
|
|
1102
1092
|
|
|
1103
|
-
request_url = self.
|
|
1093
|
+
request_url = self.groups_url() + "/" + group + "/memberof"
|
|
1104
1094
|
|
|
1105
|
-
logger.
|
|
1106
|
-
"Adding
|
|
1107
|
-
|
|
1108
|
-
|
|
1095
|
+
logger.debug(
|
|
1096
|
+
"Adding group -> '%s' to parent group -> '%s'; calling -> %s",
|
|
1097
|
+
group,
|
|
1098
|
+
parent_group,
|
|
1109
1099
|
request_url,
|
|
1110
1100
|
)
|
|
1111
1101
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
)
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1124
|
-
elif response.status_code == 401 and retries == 0:
|
|
1125
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1126
|
-
self.authenticate(revalidate=True)
|
|
1127
|
-
retries += 1
|
|
1128
|
-
else:
|
|
1129
|
-
logger.error(
|
|
1130
|
-
"Failed to add user partition -> %s; error -> %s (%s)",
|
|
1131
|
-
name,
|
|
1132
|
-
response.text,
|
|
1133
|
-
response.status_code,
|
|
1134
|
-
)
|
|
1135
|
-
return None
|
|
1136
|
-
|
|
1137
|
-
# end method definition
|
|
1138
|
-
|
|
1139
|
-
def get_partition(self, name: str, show_error: bool = True) -> dict | None:
|
|
1140
|
-
"""Get an existing user partition from OTDS
|
|
1141
|
-
|
|
1142
|
-
Args:
|
|
1143
|
-
name (str): name of the partition to retrieve
|
|
1144
|
-
show_error (bool, optional): whether or not we want to log an error
|
|
1145
|
-
if partion is not found
|
|
1146
|
-
Returns:
|
|
1147
|
-
dict: Request response or None if the REST call fails.
|
|
1148
|
-
"""
|
|
1149
|
-
|
|
1150
|
-
request_url = "{}/{}".format(self.config()["partitionUrl"], name)
|
|
1102
|
+
# OTDS delivers an empty response.text for the API, so we don't parse it here:
|
|
1103
|
+
response = self.do_request(
|
|
1104
|
+
url=request_url,
|
|
1105
|
+
method="POST",
|
|
1106
|
+
json_data=group_to_parent_group_post_body_json,
|
|
1107
|
+
timeout=None,
|
|
1108
|
+
failure_message="Failed to add group -> '{}' to parent group -> '{}'".format(
|
|
1109
|
+
group, parent_group
|
|
1110
|
+
),
|
|
1111
|
+
parse_request_response=False,
|
|
1112
|
+
)
|
|
1151
1113
|
|
|
1152
|
-
|
|
1114
|
+
if response and response.ok:
|
|
1115
|
+
return True
|
|
1153
1116
|
|
|
1154
|
-
|
|
1155
|
-
while True:
|
|
1156
|
-
response = requests.get(
|
|
1157
|
-
url=request_url,
|
|
1158
|
-
headers=REQUEST_HEADERS,
|
|
1159
|
-
cookies=self.cookie(),
|
|
1160
|
-
timeout=None,
|
|
1161
|
-
)
|
|
1162
|
-
if response.ok:
|
|
1163
|
-
return self.parse_request_response(response)
|
|
1164
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1165
|
-
elif response.status_code == 401 and retries == 0:
|
|
1166
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1167
|
-
self.authenticate(revalidate=True)
|
|
1168
|
-
retries += 1
|
|
1169
|
-
else:
|
|
1170
|
-
if show_error:
|
|
1171
|
-
logger.error(
|
|
1172
|
-
"Failed to get partition -> %s; warning -> %s (%s)",
|
|
1173
|
-
name,
|
|
1174
|
-
response.text,
|
|
1175
|
-
response.status_code,
|
|
1176
|
-
)
|
|
1177
|
-
return None
|
|
1117
|
+
return False
|
|
1178
1118
|
|
|
1179
1119
|
# end method definition
|
|
1180
1120
|
|
|
1181
|
-
def
|
|
1121
|
+
def add_resource(
|
|
1182
1122
|
self,
|
|
1183
|
-
partition: str,
|
|
1184
1123
|
name: str,
|
|
1185
1124
|
description: str = "",
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1125
|
+
display_name: str = "",
|
|
1126
|
+
allow_impersonation: bool = True,
|
|
1127
|
+
resource_id: str | None = None,
|
|
1128
|
+
secret: str | None = None, # needs to be 16 bytes!
|
|
1129
|
+
additional_payload: dict | None = None,
|
|
1189
1130
|
) -> dict | None:
|
|
1190
|
-
"""Add
|
|
1131
|
+
"""Add an OTDS resource
|
|
1191
1132
|
|
|
1192
1133
|
Args:
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
last_name (str, optional): last name of the new user
|
|
1198
|
-
email (str, optional): email address of the new user
|
|
1134
|
+
name (str): name of the new OTDS resource
|
|
1135
|
+
description (str): description of the new OTDS resource
|
|
1136
|
+
display_name (str): display name of the OTDS resource
|
|
1137
|
+
additional_payload (dict, optional): additional values for the json payload
|
|
1199
1138
|
Returns:
|
|
1200
|
-
dict: Request response or None if the
|
|
1139
|
+
dict: Request response (dictionary) or None if the REST call fails.
|
|
1201
1140
|
"""
|
|
1202
1141
|
|
|
1203
|
-
|
|
1204
|
-
"
|
|
1205
|
-
"values": [
|
|
1206
|
-
{"name": "sn", "values": [last_name]},
|
|
1207
|
-
{"name": "givenName", "values": [first_name]},
|
|
1208
|
-
{"name": "mail", "values": [email]},
|
|
1209
|
-
],
|
|
1210
|
-
"name": name,
|
|
1142
|
+
resource_post_body = {
|
|
1143
|
+
"resourceName": name,
|
|
1211
1144
|
"description": description,
|
|
1145
|
+
"displayName": display_name,
|
|
1146
|
+
"allowImpersonation": allow_impersonation,
|
|
1212
1147
|
}
|
|
1213
1148
|
|
|
1214
|
-
|
|
1149
|
+
if resource_id and not secret:
|
|
1150
|
+
logger.error(
|
|
1151
|
+
"A resource ID can only be specified if a secret value is also provided!"
|
|
1152
|
+
)
|
|
1153
|
+
return None
|
|
1154
|
+
|
|
1155
|
+
if resource_id:
|
|
1156
|
+
resource_post_body["resourceID"] = resource_id
|
|
1157
|
+
if secret:
|
|
1158
|
+
if len(secret) != 24 or not secret.endswith("=="):
|
|
1159
|
+
logger.warning(
|
|
1160
|
+
"The secret should by 24 characters long and should end with '=='"
|
|
1161
|
+
)
|
|
1162
|
+
resource_post_body["secretKey"] = secret
|
|
1163
|
+
|
|
1164
|
+
# Check if there's additional payload for the body provided to handle special cases:
|
|
1165
|
+
if additional_payload:
|
|
1166
|
+
# Merge additional payload:
|
|
1167
|
+
resource_post_body.update(additional_payload)
|
|
1215
1168
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1169
|
+
request_url = self.config()["resourceUrl"]
|
|
1170
|
+
|
|
1171
|
+
logger.debug(
|
|
1172
|
+
"Adding resource -> '%s' ('%s'); calling -> %s",
|
|
1218
1173
|
name,
|
|
1219
|
-
|
|
1174
|
+
description,
|
|
1220
1175
|
request_url,
|
|
1221
1176
|
)
|
|
1222
|
-
logger.debug("User Attributes -> %s", str(userPostBodyJson))
|
|
1223
1177
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
timeout=None,
|
|
1232
|
-
)
|
|
1233
|
-
if response.ok:
|
|
1234
|
-
return self.parse_request_response(response)
|
|
1235
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1236
|
-
elif response.status_code == 401 and retries == 0:
|
|
1237
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1238
|
-
self.authenticate(revalidate=True)
|
|
1239
|
-
retries += 1
|
|
1240
|
-
else:
|
|
1241
|
-
logger.error(
|
|
1242
|
-
"Failed to add user -> %s; error -> %s (%s)",
|
|
1243
|
-
name,
|
|
1244
|
-
response.text,
|
|
1245
|
-
response.status_code,
|
|
1246
|
-
)
|
|
1247
|
-
return None
|
|
1178
|
+
return self.do_request(
|
|
1179
|
+
url=request_url,
|
|
1180
|
+
method="POST",
|
|
1181
|
+
json_data=resource_post_body,
|
|
1182
|
+
timeout=None,
|
|
1183
|
+
failure_message="Failed to add resource -> '{}'".format(name),
|
|
1184
|
+
)
|
|
1248
1185
|
|
|
1249
1186
|
# end method definition
|
|
1250
1187
|
|
|
1251
|
-
def
|
|
1252
|
-
"""Get
|
|
1188
|
+
def get_resource(self, name: str, show_error: bool = False) -> dict | None:
|
|
1189
|
+
"""Get an existing OTDS resource
|
|
1253
1190
|
|
|
1254
1191
|
Args:
|
|
1255
|
-
|
|
1256
|
-
|
|
1192
|
+
name (str): name of the new OTDS resource
|
|
1193
|
+
show_error (bool, optional): treat as error if resource is not found
|
|
1257
1194
|
Returns:
|
|
1258
|
-
dict: Request response or None if the
|
|
1195
|
+
dict: Request response or None if the REST call fails.
|
|
1196
|
+
|
|
1197
|
+
Example:
|
|
1198
|
+
{
|
|
1199
|
+
'resourceName': 'cs',
|
|
1200
|
+
'id': 'cs',
|
|
1201
|
+
'description': 'Content Server',
|
|
1202
|
+
'displayName': 'IDEA-TE DEV - Extended ECM 24.4.0',
|
|
1203
|
+
'resourceID': 'd441e5cb-68ef-4cb7-a8a0-037ba6b35522',
|
|
1204
|
+
'resourceState': 1,
|
|
1205
|
+
'userSynchronizationState': True,
|
|
1206
|
+
'resourceDN': 'oTResource=d441e5cb-68ef-4cb7-a8a0-037ba6b35522,dc=identity,dc=opentext,dc=net',
|
|
1207
|
+
'resourceType': 'cs',
|
|
1208
|
+
'accessRoleList': [{...}],
|
|
1209
|
+
'impersonateList': None,
|
|
1210
|
+
'impersonateAnonymousList': None,
|
|
1211
|
+
'pcCreatePermissionAllowed': True,
|
|
1212
|
+
'pcModifyPermissionAllowed': True,
|
|
1213
|
+
'pcDeletePermissionAllowed': True,
|
|
1214
|
+
'logoutURL': 'https://otawp.dev.idea-te.eimdemo.com/home/system/wcp/sso/sso_logout.htm',
|
|
1215
|
+
'logoutMethod': 'GET',
|
|
1216
|
+
'allowImpersonation': True,
|
|
1217
|
+
'connectionHealthy': True,
|
|
1218
|
+
'connectorName': 'Content Server',
|
|
1219
|
+
'connectorid': 'cs',
|
|
1220
|
+
'userAttributeMapping': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}],
|
|
1221
|
+
'groupAttributeMapping': [{...}, {...}],
|
|
1222
|
+
'connectionParamInfo': [{...}, {...}, {...}, {...}, {...}, {...}, {...}],
|
|
1223
|
+
'logonStyle': None,
|
|
1224
|
+
'logonUXVersion': 0
|
|
1225
|
+
}
|
|
1259
1226
|
"""
|
|
1260
1227
|
|
|
1261
|
-
request_url = self.
|
|
1228
|
+
request_url = "{}/{}".format(self.config()["resourceUrl"], name)
|
|
1262
1229
|
|
|
1263
|
-
logger.
|
|
1264
|
-
"Get user -> %s in partition -> %s; calling -> %s",
|
|
1265
|
-
user_id,
|
|
1266
|
-
partition,
|
|
1267
|
-
request_url,
|
|
1268
|
-
)
|
|
1230
|
+
logger.debug("Get resource -> '%s'; calling -> %s", name, request_url)
|
|
1269
1231
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
)
|
|
1278
|
-
if response.ok:
|
|
1279
|
-
return self.parse_request_response(response)
|
|
1280
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1281
|
-
elif response.status_code == 401 and retries == 0:
|
|
1282
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1283
|
-
self.authenticate(revalidate=True)
|
|
1284
|
-
retries += 1
|
|
1285
|
-
else:
|
|
1286
|
-
logger.error(
|
|
1287
|
-
"Failed to get user -> %s; error -> %s (%s)",
|
|
1288
|
-
user_id,
|
|
1289
|
-
response.text,
|
|
1290
|
-
response.status_code,
|
|
1291
|
-
)
|
|
1292
|
-
return None
|
|
1232
|
+
return self.do_request(
|
|
1233
|
+
url=request_url,
|
|
1234
|
+
method="GET",
|
|
1235
|
+
timeout=None,
|
|
1236
|
+
failure_message="Failed to get resource -> '{}'".format(name),
|
|
1237
|
+
show_error=show_error,
|
|
1238
|
+
)
|
|
1293
1239
|
|
|
1294
1240
|
# end method definition
|
|
1295
1241
|
|
|
1296
|
-
def
|
|
1297
|
-
|
|
1242
|
+
def update_resource(
|
|
1243
|
+
self, name: str, resource: object, show_error: bool = True
|
|
1244
|
+
) -> dict | None:
|
|
1245
|
+
"""Update an existing OTDS resource
|
|
1298
1246
|
|
|
1299
1247
|
Args:
|
|
1300
|
-
|
|
1301
|
-
|
|
1248
|
+
name (str): name of the new OTDS resource
|
|
1249
|
+
resource (object): updated resource object of get_resource called before
|
|
1250
|
+
show_error (bool, optional): treat as error if resource is not found
|
|
1302
1251
|
Returns:
|
|
1303
|
-
dict: Request response or None if the
|
|
1252
|
+
dict: Request response (json) or None if the REST call fails.
|
|
1304
1253
|
"""
|
|
1305
1254
|
|
|
1306
|
-
|
|
1307
|
-
query = {}
|
|
1308
|
-
if limit:
|
|
1309
|
-
query["limit"] = limit
|
|
1310
|
-
if partition:
|
|
1311
|
-
query["where_partition_name"] = partition
|
|
1312
|
-
|
|
1313
|
-
encodedQuery = urllib.parse.urlencode(query, doseq=True)
|
|
1314
|
-
|
|
1315
|
-
request_url = self.users_url()
|
|
1316
|
-
if query:
|
|
1317
|
-
request_url += "?{}".format(encodedQuery)
|
|
1255
|
+
request_url = "{}/{}".format(self.config()["resourceUrl"], name)
|
|
1318
1256
|
|
|
1319
|
-
|
|
1320
|
-
logger.info(
|
|
1321
|
-
"Get all users in partition -> %s (limit -> %s); calling -> %s",
|
|
1322
|
-
partition,
|
|
1323
|
-
limit,
|
|
1324
|
-
request_url,
|
|
1325
|
-
)
|
|
1326
|
-
else:
|
|
1327
|
-
logger.info(
|
|
1328
|
-
"Get all users (limit -> %s); calling -> %s",
|
|
1329
|
-
limit,
|
|
1330
|
-
request_url,
|
|
1331
|
-
)
|
|
1257
|
+
logger.debug("Updating resource -> '%s'; calling -> %s", name, request_url)
|
|
1332
1258
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
if response.ok:
|
|
1342
|
-
return self.parse_request_response(response)
|
|
1343
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1344
|
-
elif response.status_code == 401 and retries == 0:
|
|
1345
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1346
|
-
self.authenticate(revalidate=True)
|
|
1347
|
-
retries += 1
|
|
1348
|
-
else:
|
|
1349
|
-
if partition:
|
|
1350
|
-
logger.error(
|
|
1351
|
-
"Failed to get users in partition -> %s; error -> %s (%s)",
|
|
1352
|
-
partition,
|
|
1353
|
-
response.text,
|
|
1354
|
-
response.status_code,
|
|
1355
|
-
)
|
|
1356
|
-
else:
|
|
1357
|
-
logger.error(
|
|
1358
|
-
"Failed to get users; error -> %s (%s)",
|
|
1359
|
-
response.text,
|
|
1360
|
-
response.status_code,
|
|
1361
|
-
)
|
|
1362
|
-
return None
|
|
1259
|
+
return self.do_request(
|
|
1260
|
+
url=request_url,
|
|
1261
|
+
method="PUT",
|
|
1262
|
+
json_data=resource,
|
|
1263
|
+
timeout=None,
|
|
1264
|
+
failure_message="Failed to update resource -> '{}'".format(name),
|
|
1265
|
+
show_error=show_error,
|
|
1266
|
+
)
|
|
1363
1267
|
|
|
1364
1268
|
# end method definition
|
|
1365
1269
|
|
|
1366
|
-
def
|
|
1367
|
-
|
|
1368
|
-
) -> dict | None:
|
|
1369
|
-
"""Update a user attribute with a new value
|
|
1270
|
+
def activate_resource(self, resource_id: str) -> dict | None:
|
|
1271
|
+
"""Activate an OTDS resource
|
|
1370
1272
|
|
|
1371
1273
|
Args:
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
attribute_value (str): new value of the attribute
|
|
1376
|
-
Return:
|
|
1377
|
-
dict: Request response or None if the update fails.
|
|
1274
|
+
resource_id (str): ID of the OTDS resource
|
|
1275
|
+
Returns:
|
|
1276
|
+
dict: Request response (json) or None if the REST call fails.
|
|
1378
1277
|
"""
|
|
1379
1278
|
|
|
1380
|
-
|
|
1381
|
-
userPatchBodyJson = {
|
|
1382
|
-
"userPartitionID": partition,
|
|
1383
|
-
attribute_name: attribute_value,
|
|
1384
|
-
}
|
|
1385
|
-
else:
|
|
1386
|
-
userPatchBodyJson = {
|
|
1387
|
-
"userPartitionID": partition,
|
|
1388
|
-
"values": [{"name": attribute_name, "values": [attribute_value]}],
|
|
1389
|
-
}
|
|
1279
|
+
resource_post_body_json = {}
|
|
1390
1280
|
|
|
1391
|
-
request_url = self.
|
|
1281
|
+
request_url = "{}/{}/activate".format(self.config()["resourceUrl"], resource_id)
|
|
1392
1282
|
|
|
1393
|
-
logger.
|
|
1394
|
-
"
|
|
1395
|
-
user_id,
|
|
1396
|
-
attribute_name,
|
|
1397
|
-
attribute_value,
|
|
1398
|
-
request_url,
|
|
1283
|
+
logger.debug(
|
|
1284
|
+
"Activating resource -> '%s'; calling -> %s", resource_id, request_url
|
|
1399
1285
|
)
|
|
1400
1286
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
timeout=None,
|
|
1409
|
-
)
|
|
1410
|
-
if response.ok:
|
|
1411
|
-
return self.parse_request_response(response)
|
|
1412
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1413
|
-
elif response.status_code == 401 and retries == 0:
|
|
1414
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1415
|
-
self.authenticate(revalidate=True)
|
|
1416
|
-
retries += 1
|
|
1417
|
-
else:
|
|
1418
|
-
logger.error(
|
|
1419
|
-
"Failed to update user -> %s; error -> %s (%s)",
|
|
1420
|
-
user_id,
|
|
1421
|
-
response.text,
|
|
1422
|
-
response.status_code,
|
|
1423
|
-
)
|
|
1424
|
-
return None
|
|
1287
|
+
return self.do_request(
|
|
1288
|
+
url=request_url,
|
|
1289
|
+
method="POST",
|
|
1290
|
+
json_data=resource_post_body_json,
|
|
1291
|
+
timeout=None,
|
|
1292
|
+
failure_message="Failed to activate resource -> '{}'".format(resource_id),
|
|
1293
|
+
)
|
|
1425
1294
|
|
|
1426
1295
|
# end method definition
|
|
1427
1296
|
|
|
1428
|
-
def
|
|
1429
|
-
"""
|
|
1297
|
+
def get_access_roles(self) -> dict | None:
|
|
1298
|
+
"""Get a list of all OTDS access roles
|
|
1430
1299
|
|
|
1431
1300
|
Args:
|
|
1432
|
-
|
|
1433
|
-
user_id (str): Id (= login name) of the user
|
|
1301
|
+
None
|
|
1434
1302
|
Returns:
|
|
1435
|
-
|
|
1303
|
+
dict: Request response or None if the REST call fails.
|
|
1436
1304
|
"""
|
|
1437
1305
|
|
|
1438
|
-
request_url = self.
|
|
1306
|
+
request_url = self.config()["accessRoleUrl"]
|
|
1439
1307
|
|
|
1440
|
-
logger.
|
|
1441
|
-
"Delete user -> %s in partition -> %s; calling -> %s",
|
|
1442
|
-
user_id,
|
|
1443
|
-
partition,
|
|
1444
|
-
request_url,
|
|
1445
|
-
)
|
|
1308
|
+
logger.debug("Retrieving access roles; calling -> %s", request_url)
|
|
1446
1309
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
timeout=None,
|
|
1454
|
-
)
|
|
1455
|
-
if response.ok:
|
|
1456
|
-
return True
|
|
1457
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1458
|
-
elif response.status_code == 401 and retries == 0:
|
|
1459
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1460
|
-
self.authenticate(revalidate=True)
|
|
1461
|
-
retries += 1
|
|
1462
|
-
else:
|
|
1463
|
-
logger.error(
|
|
1464
|
-
"Failed to delete user -> %s; error -> %s (%s)",
|
|
1465
|
-
user_id,
|
|
1466
|
-
response.text,
|
|
1467
|
-
response.status_code,
|
|
1468
|
-
)
|
|
1469
|
-
return False
|
|
1310
|
+
return self.do_request(
|
|
1311
|
+
url=request_url,
|
|
1312
|
+
method="GET",
|
|
1313
|
+
timeout=None,
|
|
1314
|
+
failure_message="Failed to get access roles",
|
|
1315
|
+
)
|
|
1470
1316
|
|
|
1471
1317
|
# end method definition
|
|
1472
1318
|
|
|
1473
|
-
def
|
|
1474
|
-
"""
|
|
1319
|
+
def get_access_role(self, access_role: str) -> dict | None:
|
|
1320
|
+
"""Get an OTDS access role
|
|
1475
1321
|
|
|
1476
1322
|
Args:
|
|
1477
|
-
|
|
1478
|
-
password (str): new password of the user
|
|
1323
|
+
name (str): name of the access role
|
|
1479
1324
|
Returns:
|
|
1480
|
-
|
|
1325
|
+
dict: Request response (json) or None if the REST call fails.
|
|
1481
1326
|
"""
|
|
1482
1327
|
|
|
1483
|
-
|
|
1328
|
+
request_url = self.config()["accessRoleUrl"] + "/" + access_role
|
|
1484
1329
|
|
|
1485
|
-
|
|
1330
|
+
logger.debug("Get access role -> '%s'; calling -> %s", access_role, request_url)
|
|
1486
1331
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1332
|
+
return self.do_request(
|
|
1333
|
+
url=request_url,
|
|
1334
|
+
method="GET",
|
|
1335
|
+
timeout=None,
|
|
1336
|
+
failure_message="Failed to get access role -> '{}'".format(access_role),
|
|
1489
1337
|
)
|
|
1490
1338
|
|
|
1491
|
-
retries = 0
|
|
1492
|
-
while True:
|
|
1493
|
-
response = requests.put(
|
|
1494
|
-
url=request_url,
|
|
1495
|
-
json=userPostBodyJson,
|
|
1496
|
-
headers=REQUEST_HEADERS,
|
|
1497
|
-
cookies=self.cookie(),
|
|
1498
|
-
timeout=None,
|
|
1499
|
-
)
|
|
1500
|
-
if response.ok:
|
|
1501
|
-
return True
|
|
1502
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1503
|
-
elif response.status_code == 401 and retries == 0:
|
|
1504
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1505
|
-
self.authenticate(revalidate=True)
|
|
1506
|
-
retries += 1
|
|
1507
|
-
else:
|
|
1508
|
-
logger.error(
|
|
1509
|
-
"Failed to reset password for user -> %s; error -> %s (%s)",
|
|
1510
|
-
user_id,
|
|
1511
|
-
response.text,
|
|
1512
|
-
response.status_code,
|
|
1513
|
-
)
|
|
1514
|
-
return False
|
|
1515
|
-
|
|
1516
1339
|
# end method definition
|
|
1517
1340
|
|
|
1518
|
-
def
|
|
1519
|
-
|
|
1341
|
+
def add_partition_to_access_role(
|
|
1342
|
+
self, access_role: str, partition: str, location: str = ""
|
|
1343
|
+
) -> bool:
|
|
1344
|
+
"""Add an OTDS partition to an OTDS access role
|
|
1520
1345
|
|
|
1521
1346
|
Args:
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1347
|
+
access_role (str): name of the OTDS access role
|
|
1348
|
+
partition (str): name of the partition
|
|
1349
|
+
location (str, optional): this is kind of a unique identifier DN (Distinguished Name)
|
|
1350
|
+
most of the times you will want to keep it to empty string ("")
|
|
1525
1351
|
Returns:
|
|
1526
|
-
|
|
1352
|
+
bool: True if partition is in access role or has been successfully added.
|
|
1353
|
+
False if partition has been not been added (error)
|
|
1527
1354
|
"""
|
|
1528
1355
|
|
|
1529
|
-
|
|
1530
|
-
"
|
|
1531
|
-
"name": name,
|
|
1532
|
-
"description": description,
|
|
1356
|
+
access_role_post_body_json = {
|
|
1357
|
+
"userPartitions": [{"name": partition, "location": location}]
|
|
1533
1358
|
}
|
|
1534
1359
|
|
|
1535
|
-
request_url =
|
|
1360
|
+
request_url = "{}/{}/members".format(
|
|
1361
|
+
self.config()["accessRoleUrl"], access_role
|
|
1362
|
+
)
|
|
1536
1363
|
|
|
1537
|
-
logger.
|
|
1538
|
-
"
|
|
1539
|
-
name,
|
|
1364
|
+
logger.debug(
|
|
1365
|
+
"Add user partition -> '%s' to access role -> '%s'; calling -> %s",
|
|
1540
1366
|
partition,
|
|
1367
|
+
access_role,
|
|
1541
1368
|
request_url,
|
|
1542
1369
|
)
|
|
1543
|
-
logger.debug("Group Attributes -> %s", str(groupPostBodyJson))
|
|
1544
1370
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
retries += 1
|
|
1561
|
-
else:
|
|
1562
|
-
logger.error(
|
|
1563
|
-
"Failed to add group -> %s; error -> %s (%s)",
|
|
1564
|
-
name,
|
|
1565
|
-
response.text,
|
|
1566
|
-
response.status_code,
|
|
1567
|
-
)
|
|
1568
|
-
return None
|
|
1371
|
+
response = self.do_request(
|
|
1372
|
+
url=request_url,
|
|
1373
|
+
method="POST",
|
|
1374
|
+
json_data=access_role_post_body_json,
|
|
1375
|
+
timeout=None,
|
|
1376
|
+
failure_message="Failed to add partition -> '{}' to access role -> '{}'".format(
|
|
1377
|
+
partition, access_role
|
|
1378
|
+
),
|
|
1379
|
+
parse_request_response=False,
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
if response and response.ok:
|
|
1383
|
+
return True
|
|
1384
|
+
|
|
1385
|
+
return False
|
|
1569
1386
|
|
|
1570
1387
|
# end method definition
|
|
1571
1388
|
|
|
1572
|
-
def
|
|
1573
|
-
|
|
1389
|
+
def add_user_to_access_role(
|
|
1390
|
+
self, access_role: str, user_id: str, location: str = ""
|
|
1391
|
+
) -> bool:
|
|
1392
|
+
"""Add an OTDS user to an OTDS access role
|
|
1574
1393
|
|
|
1575
1394
|
Args:
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
'name': 'Sales',
|
|
1584
|
-
'location': 'oTGroup=3f921294-b92a-4c9e-bf7c-b50df16bb937,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net',
|
|
1585
|
-
'id': 'Sales@Content Server Members',
|
|
1586
|
-
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...],
|
|
1587
|
-
'description': None,
|
|
1588
|
-
'uuid': '3f921294-b92a-4c9e-bf7c-b50df16bb937',
|
|
1589
|
-
'objectClass': 'oTGroup',
|
|
1590
|
-
'customAttributes': None,
|
|
1591
|
-
'originUUID': None,
|
|
1592
|
-
'urlId': 'Sales@Content Server Members',
|
|
1593
|
-
'urlLocation': 'oTGroup=3f921294-b92a-4c9e-bf7c-b50df16bb937,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net'
|
|
1594
|
-
}
|
|
1395
|
+
access_role (str): name of the OTDS access role
|
|
1396
|
+
user_id (str): ID of the user (= login name)
|
|
1397
|
+
location (str, optional): this is kind of a unique identifier DN (Distinguished Name)
|
|
1398
|
+
most of the times you will want to keep it to empty string ("")
|
|
1399
|
+
Returns:
|
|
1400
|
+
bool: True if user is in access role or has been successfully added.
|
|
1401
|
+
False if user has not been added (error)
|
|
1595
1402
|
"""
|
|
1596
1403
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1404
|
+
# get existing members to check if user is already a member:
|
|
1405
|
+
access_roles_get_response = self.get_access_role(access_role)
|
|
1406
|
+
if not access_roles_get_response:
|
|
1407
|
+
return False
|
|
1600
1408
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
if response.ok:
|
|
1610
|
-
return self.parse_request_response(response)
|
|
1611
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1612
|
-
elif response.status_code == 401 and retries == 0:
|
|
1613
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1614
|
-
self.authenticate(revalidate=True)
|
|
1615
|
-
retries += 1
|
|
1616
|
-
else:
|
|
1617
|
-
logger.error(
|
|
1618
|
-
"Failed to get group -> %s; error -> %s (%s)",
|
|
1619
|
-
group,
|
|
1620
|
-
response.text,
|
|
1621
|
-
response.status_code,
|
|
1409
|
+
# Checking if user already added to access role
|
|
1410
|
+
accessRoleUsers = access_roles_get_response["accessRoleMembers"]["users"]
|
|
1411
|
+
for user in accessRoleUsers:
|
|
1412
|
+
if user["displayName"] == user_id:
|
|
1413
|
+
logger.debug(
|
|
1414
|
+
"User -> '%s' already added to access role -> '%s'",
|
|
1415
|
+
user_id,
|
|
1416
|
+
access_role,
|
|
1622
1417
|
)
|
|
1623
|
-
return
|
|
1418
|
+
return True
|
|
1419
|
+
|
|
1420
|
+
logger.debug(
|
|
1421
|
+
"User -> '%s' is not yet in access role -> '%s' - adding...",
|
|
1422
|
+
user_id,
|
|
1423
|
+
access_role,
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1426
|
+
# create payload for REST call:
|
|
1427
|
+
access_role_post_body_json = {
|
|
1428
|
+
"users": [{"name": user_id, "location": location}]
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
request_url = "{}/{}/members".format(
|
|
1432
|
+
self.config()["accessRoleUrl"], access_role
|
|
1433
|
+
)
|
|
1434
|
+
|
|
1435
|
+
logger.debug(
|
|
1436
|
+
"Add user -> %s to access role -> %s; calling -> %s",
|
|
1437
|
+
user_id,
|
|
1438
|
+
access_role,
|
|
1439
|
+
request_url,
|
|
1440
|
+
)
|
|
1441
|
+
|
|
1442
|
+
response = self.do_request(
|
|
1443
|
+
url=request_url,
|
|
1444
|
+
method="POST",
|
|
1445
|
+
json_data=access_role_post_body_json,
|
|
1446
|
+
timeout=None,
|
|
1447
|
+
failure_message="Failed to add user -> '{}' to access role -> '{}'".format(
|
|
1448
|
+
user_id, access_role
|
|
1449
|
+
),
|
|
1450
|
+
parse_request_response=False,
|
|
1451
|
+
)
|
|
1452
|
+
|
|
1453
|
+
if response and response.ok:
|
|
1454
|
+
return True
|
|
1455
|
+
|
|
1456
|
+
return False
|
|
1624
1457
|
|
|
1625
1458
|
# end method definition
|
|
1626
1459
|
|
|
1627
|
-
def
|
|
1628
|
-
|
|
1460
|
+
def add_group_to_access_role(
|
|
1461
|
+
self, access_role: str, group: str, location: str = ""
|
|
1462
|
+
) -> bool:
|
|
1463
|
+
"""Add an OTDS group to an OTDS access role
|
|
1629
1464
|
|
|
1630
1465
|
Args:
|
|
1631
|
-
|
|
1632
|
-
group (str): name of the
|
|
1466
|
+
access_role (str): name of the OTDS access role
|
|
1467
|
+
group (str): name of the group
|
|
1468
|
+
location (str, optional): this is kind of a unique identifier DN (Distinguished Name)
|
|
1469
|
+
most of the times you will want to keep it to empty string ("")
|
|
1633
1470
|
Returns:
|
|
1634
|
-
bool: True
|
|
1471
|
+
bool: True if group is in access role or has been successfully added.
|
|
1472
|
+
False if group has been not been added (error)
|
|
1635
1473
|
"""
|
|
1636
1474
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1475
|
+
# get existing members to check if user is already a member:
|
|
1476
|
+
access_roles_get_response = self.get_access_role(access_role)
|
|
1477
|
+
if not access_roles_get_response:
|
|
1478
|
+
return False
|
|
1640
1479
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
response = requests.post(
|
|
1648
|
-
url=request_url,
|
|
1649
|
-
json=userToGroupPostBodyJson,
|
|
1650
|
-
headers=REQUEST_HEADERS,
|
|
1651
|
-
cookies=self.cookie(),
|
|
1652
|
-
timeout=None,
|
|
1653
|
-
)
|
|
1654
|
-
if response.ok:
|
|
1655
|
-
return True
|
|
1656
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1657
|
-
elif response.status_code == 401 and retries == 0:
|
|
1658
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1659
|
-
self.authenticate(revalidate=True)
|
|
1660
|
-
retries += 1
|
|
1661
|
-
else:
|
|
1662
|
-
logger.error(
|
|
1663
|
-
"Failed to add user -> %s to group -> %s; error -> %s (%s)",
|
|
1664
|
-
user,
|
|
1480
|
+
# Checking if group already added to access role
|
|
1481
|
+
access_role_groups = access_roles_get_response["accessRoleMembers"]["groups"]
|
|
1482
|
+
for access_role_group in access_role_groups:
|
|
1483
|
+
if access_role_group["name"] == group:
|
|
1484
|
+
logger.debug(
|
|
1485
|
+
"Group -> '%s' already added to access role -> '%s'",
|
|
1665
1486
|
group,
|
|
1666
|
-
|
|
1667
|
-
response.status_code,
|
|
1487
|
+
access_role,
|
|
1668
1488
|
)
|
|
1669
|
-
return
|
|
1489
|
+
return True
|
|
1490
|
+
|
|
1491
|
+
logger.debug(
|
|
1492
|
+
"Group -> '%s' is not yet in access role -> '%s' - adding...",
|
|
1493
|
+
group,
|
|
1494
|
+
access_role,
|
|
1495
|
+
)
|
|
1496
|
+
|
|
1497
|
+
# create payload for REST call:
|
|
1498
|
+
access_role_post_body_json = {"groups": [{"name": group, "location": location}]}
|
|
1499
|
+
|
|
1500
|
+
request_url = "{}/{}/members".format(
|
|
1501
|
+
self.config()["accessRoleUrl"], access_role
|
|
1502
|
+
)
|
|
1503
|
+
|
|
1504
|
+
logger.debug(
|
|
1505
|
+
"Add group -> '%s' to access role -> '%s'; calling -> %s",
|
|
1506
|
+
group,
|
|
1507
|
+
access_role,
|
|
1508
|
+
request_url,
|
|
1509
|
+
)
|
|
1510
|
+
|
|
1511
|
+
response = self.do_request(
|
|
1512
|
+
url=request_url,
|
|
1513
|
+
method="POST",
|
|
1514
|
+
json_data=access_role_post_body_json,
|
|
1515
|
+
timeout=None,
|
|
1516
|
+
failure_message="Failed to add group -> '{}' to access role -> '{}'".format(
|
|
1517
|
+
group, access_role
|
|
1518
|
+
),
|
|
1519
|
+
parse_request_response=False,
|
|
1520
|
+
)
|
|
1521
|
+
|
|
1522
|
+
if response and response.ok:
|
|
1523
|
+
return True
|
|
1524
|
+
|
|
1525
|
+
return False
|
|
1670
1526
|
|
|
1671
1527
|
# end method definition
|
|
1672
1528
|
|
|
1673
|
-
def
|
|
1674
|
-
|
|
1529
|
+
def update_access_role_attributes(
|
|
1530
|
+
self, name: str, attribute_list: list
|
|
1531
|
+
) -> dict | None:
|
|
1532
|
+
"""Update some attributes of an existing OTDS Access Role
|
|
1675
1533
|
|
|
1676
1534
|
Args:
|
|
1677
|
-
|
|
1678
|
-
|
|
1535
|
+
name (str): name of the existing access role
|
|
1536
|
+
attribute_list (list): list of attribute name and attribute value pairs
|
|
1537
|
+
The values need to be a list as well. Example:
|
|
1538
|
+
[{name: "pushAllGroups", values: ["True"]}]
|
|
1679
1539
|
Returns:
|
|
1680
|
-
|
|
1540
|
+
dict: Request response (json) or None if the REST call fails.
|
|
1681
1541
|
"""
|
|
1682
1542
|
|
|
1683
|
-
|
|
1543
|
+
# Return if list is empty:
|
|
1544
|
+
if not attribute_list:
|
|
1545
|
+
return None
|
|
1684
1546
|
|
|
1685
|
-
|
|
1547
|
+
# create payload for REST call:
|
|
1548
|
+
access_role = self.get_access_role(name)
|
|
1549
|
+
if not access_role:
|
|
1550
|
+
logger.error("Failed to get access role -> '%s'", name)
|
|
1551
|
+
return None
|
|
1686
1552
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1553
|
+
access_role_put_body_json = {"attributes": attribute_list}
|
|
1554
|
+
|
|
1555
|
+
request_url = "{}/{}/attributes".format(self.config()["accessRoleUrl"], name)
|
|
1556
|
+
|
|
1557
|
+
logger.debug(
|
|
1558
|
+
"Update access role -> '%s' with attributes -> %s; calling -> %s",
|
|
1559
|
+
name,
|
|
1560
|
+
str(access_role_put_body_json),
|
|
1691
1561
|
request_url,
|
|
1692
1562
|
)
|
|
1693
1563
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
timeout=None,
|
|
1702
|
-
)
|
|
1703
|
-
|
|
1704
|
-
if response.ok:
|
|
1705
|
-
return True
|
|
1706
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1707
|
-
elif response.status_code == 401 and retries == 0:
|
|
1708
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1709
|
-
self.authenticate(revalidate=True)
|
|
1710
|
-
retries += 1
|
|
1711
|
-
else:
|
|
1712
|
-
logger.error(
|
|
1713
|
-
"Failed to add group -> %s to parent group -> %s; error -> %s (%s)",
|
|
1714
|
-
group,
|
|
1715
|
-
parent_group,
|
|
1716
|
-
response.text,
|
|
1717
|
-
response.status_code,
|
|
1718
|
-
)
|
|
1719
|
-
return False
|
|
1564
|
+
return self.do_request(
|
|
1565
|
+
url=request_url,
|
|
1566
|
+
method="PUT",
|
|
1567
|
+
json_data=access_role_put_body_json,
|
|
1568
|
+
timeout=None,
|
|
1569
|
+
failure_message="Failed to update access role -> '{}'".format(access_role),
|
|
1570
|
+
)
|
|
1720
1571
|
|
|
1721
1572
|
# end method definition
|
|
1722
1573
|
|
|
1723
|
-
def
|
|
1574
|
+
def add_license_to_resource(
|
|
1724
1575
|
self,
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1576
|
+
path_to_license_file: str,
|
|
1577
|
+
product_name: str,
|
|
1578
|
+
product_description: str,
|
|
1579
|
+
resource_id: str,
|
|
1580
|
+
update: bool = True,
|
|
1729
1581
|
) -> dict | None:
|
|
1730
|
-
"""Add an OTDS resource
|
|
1582
|
+
"""Add a product license to an OTDS resource.
|
|
1731
1583
|
|
|
1732
1584
|
Args:
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1585
|
+
path_to_license_file (str): fully qualified filename of the license file
|
|
1586
|
+
product_name (str): product name
|
|
1587
|
+
product_description (str): product description
|
|
1588
|
+
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
1589
|
+
update (bool, optional): whether or not an existing license should be updated (default = True)
|
|
1737
1590
|
Returns:
|
|
1738
|
-
dict: Request response (dictionary) or None if the REST call fails
|
|
1591
|
+
dict: Request response (dictionary) or None if the REST call fails
|
|
1739
1592
|
"""
|
|
1740
1593
|
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
"
|
|
1744
|
-
|
|
1745
|
-
|
|
1594
|
+
logger.debug("Reading license file -> '%s'...", path_to_license_file)
|
|
1595
|
+
try:
|
|
1596
|
+
with open(path_to_license_file, "rt", encoding="UTF-8") as license_file:
|
|
1597
|
+
license_content = license_file.read()
|
|
1598
|
+
except IOError as exception:
|
|
1599
|
+
logger.error(
|
|
1600
|
+
"Error opening license file -> '%s'; error -> %s",
|
|
1601
|
+
path_to_license_file,
|
|
1602
|
+
exception.strerror,
|
|
1603
|
+
)
|
|
1604
|
+
return None
|
|
1746
1605
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1606
|
+
license_post_body_json = {
|
|
1607
|
+
"description": product_description,
|
|
1608
|
+
"name": product_name,
|
|
1609
|
+
"values": [
|
|
1610
|
+
{"name": "oTLicenseFile", "values": license_content},
|
|
1611
|
+
{"name": "oTLicenseResource", "values": resource_id},
|
|
1612
|
+
{"name": "oTLicenseFingerprintGenerator", "values": [None]},
|
|
1613
|
+
],
|
|
1614
|
+
}
|
|
1751
1615
|
|
|
1752
|
-
request_url = self.
|
|
1616
|
+
request_url = self.license_url()
|
|
1617
|
+
# Check if we want to update an existing license:
|
|
1618
|
+
if update:
|
|
1619
|
+
existing_license = self.get_license_for_resource(resource_id)
|
|
1620
|
+
if existing_license:
|
|
1621
|
+
request_url += "/" + existing_license[0]["id"]
|
|
1622
|
+
else:
|
|
1623
|
+
logger.debug(
|
|
1624
|
+
"No existing license found for resource -> '%s' - adding a new license...",
|
|
1625
|
+
resource_id,
|
|
1626
|
+
)
|
|
1627
|
+
# change strategy to create a new license:
|
|
1628
|
+
update = False
|
|
1753
1629
|
|
|
1754
|
-
logger.
|
|
1755
|
-
"Adding
|
|
1630
|
+
logger.debug(
|
|
1631
|
+
"Adding product license -> '%s' for product -> '%s' to resource ->'%s'; calling -> %s",
|
|
1632
|
+
path_to_license_file,
|
|
1633
|
+
product_description,
|
|
1634
|
+
resource_id,
|
|
1635
|
+
request_url,
|
|
1756
1636
|
)
|
|
1757
1637
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1638
|
+
if update:
|
|
1639
|
+
# Do a REST PUT call for update an existing license:
|
|
1640
|
+
return self.do_request(
|
|
1761
1641
|
url=request_url,
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
cookies=self.cookie(),
|
|
1642
|
+
method="PUT",
|
|
1643
|
+
json_data=license_post_body_json,
|
|
1765
1644
|
timeout=None,
|
|
1645
|
+
failure_message="Failed to update product license -> '{}' for product -> '{}'".format(
|
|
1646
|
+
path_to_license_file, product_description
|
|
1647
|
+
),
|
|
1648
|
+
)
|
|
1649
|
+
else:
|
|
1650
|
+
# Do a REST POST call for creation of a new license:
|
|
1651
|
+
return self.do_request(
|
|
1652
|
+
url=request_url,
|
|
1653
|
+
method="POST",
|
|
1654
|
+
json_data=license_post_body_json,
|
|
1655
|
+
timeout=None,
|
|
1656
|
+
failure_message="Failed to add product license -> '{}' for product -> '{}'".format(
|
|
1657
|
+
path_to_license_file, product_description
|
|
1658
|
+
),
|
|
1766
1659
|
)
|
|
1767
|
-
if response.ok:
|
|
1768
|
-
return self.parse_request_response(response)
|
|
1769
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1770
|
-
elif response.status_code == 401 and retries == 0:
|
|
1771
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1772
|
-
self.authenticate(revalidate=True)
|
|
1773
|
-
retries += 1
|
|
1774
|
-
else:
|
|
1775
|
-
logger.error(
|
|
1776
|
-
"Failed to add resource -> %s; error -> %s (%s)",
|
|
1777
|
-
name,
|
|
1778
|
-
response.text,
|
|
1779
|
-
response.status_code,
|
|
1780
|
-
)
|
|
1781
|
-
return None
|
|
1782
1660
|
|
|
1783
1661
|
# end method definition
|
|
1784
1662
|
|
|
1785
|
-
def
|
|
1786
|
-
"""Get
|
|
1663
|
+
def get_license_for_resource(self, resource_id: str):
|
|
1664
|
+
"""Get a product license for a resource in OTDS.
|
|
1787
1665
|
|
|
1788
1666
|
Args:
|
|
1789
|
-
|
|
1790
|
-
show_error (bool, optional): treat as error if resource is not found
|
|
1667
|
+
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
1791
1668
|
Returns:
|
|
1792
|
-
|
|
1669
|
+
Licenses for a resource or None if the REST call fails
|
|
1670
|
+
|
|
1671
|
+
licenses have this format:
|
|
1672
|
+
{
|
|
1673
|
+
'_oTLicenseType': 'NON-PRODUCTION',
|
|
1674
|
+
'_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
1675
|
+
'_oTLicenseResourceName': 'cs',
|
|
1676
|
+
'_oTLicenseProduct': 'EXTENDED_ECM',
|
|
1677
|
+
'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
1678
|
+
'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
1679
|
+
'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
1680
|
+
'description': 'CS license',
|
|
1681
|
+
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
1682
|
+
}
|
|
1793
1683
|
"""
|
|
1794
1684
|
|
|
1795
|
-
request_url =
|
|
1685
|
+
request_url = (
|
|
1686
|
+
self.license_url()
|
|
1687
|
+
+ "/assignedlicenses?resourceID="
|
|
1688
|
+
+ resource_id
|
|
1689
|
+
+ "&validOnly=false"
|
|
1690
|
+
)
|
|
1796
1691
|
|
|
1797
|
-
logger.
|
|
1692
|
+
logger.debug(
|
|
1693
|
+
"Get license for resource -> %s; calling -> %s", resource_id, request_url
|
|
1694
|
+
)
|
|
1798
1695
|
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
self.authenticate(revalidate=True)
|
|
1813
|
-
retries += 1
|
|
1814
|
-
else:
|
|
1815
|
-
# We don't necessarily want to log an error as this function
|
|
1816
|
-
# is also used in wait loops:
|
|
1817
|
-
if show_error:
|
|
1818
|
-
logger.warning(
|
|
1819
|
-
"Failed to retrieve resource -> %s; warning -> %s",
|
|
1820
|
-
name,
|
|
1821
|
-
response.text,
|
|
1822
|
-
)
|
|
1823
|
-
else:
|
|
1824
|
-
logger.info("Resource -> %s not found.", name)
|
|
1825
|
-
return None
|
|
1696
|
+
response = self.do_request(
|
|
1697
|
+
url=request_url,
|
|
1698
|
+
method="GET",
|
|
1699
|
+
timeout=None,
|
|
1700
|
+
failure_message="Failed to get license for resource -> '{}'".format(
|
|
1701
|
+
resource_id
|
|
1702
|
+
),
|
|
1703
|
+
)
|
|
1704
|
+
|
|
1705
|
+
if not response:
|
|
1706
|
+
return None
|
|
1707
|
+
|
|
1708
|
+
return response["licenseObjects"]["_licenses"]
|
|
1826
1709
|
|
|
1827
1710
|
# end method definition
|
|
1828
1711
|
|
|
1829
|
-
def
|
|
1830
|
-
|
|
1831
|
-
) -> dict | None:
|
|
1832
|
-
"""Update an existing OTDS resource
|
|
1712
|
+
def delete_license_from_resource(self, resource_id: str, license_id: str) -> bool:
|
|
1713
|
+
"""Delete a product license for a resource in OTDS.
|
|
1833
1714
|
|
|
1834
1715
|
Args:
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
show_error (bool, optional): treat as error if resource is not found
|
|
1716
|
+
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
1717
|
+
license_id (str): OTDS license ID (this is the ID not the license name!)
|
|
1838
1718
|
Returns:
|
|
1839
|
-
|
|
1719
|
+
bool: True if successful or False if the REST call fails
|
|
1840
1720
|
"""
|
|
1841
1721
|
|
|
1842
|
-
request_url = "{}/{}".format(self.
|
|
1843
|
-
|
|
1844
|
-
logger.info("Updating resource -> %s; calling -> %s", name, request_url)
|
|
1722
|
+
request_url = "{}/{}".format(self.license_url(), license_id)
|
|
1845
1723
|
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
"Failed to retrieve resource -> %s; warning -> %s",
|
|
1868
|
-
name,
|
|
1869
|
-
response.text,
|
|
1870
|
-
)
|
|
1871
|
-
else:
|
|
1872
|
-
logger.info("Resource -> %s not found.", name)
|
|
1873
|
-
return None
|
|
1724
|
+
logger.debug(
|
|
1725
|
+
"Deleting product license -> '%s' from resource -> '%s'; calling -> %s",
|
|
1726
|
+
license_id,
|
|
1727
|
+
resource_id,
|
|
1728
|
+
request_url,
|
|
1729
|
+
)
|
|
1730
|
+
|
|
1731
|
+
response = self.do_request(
|
|
1732
|
+
url=request_url,
|
|
1733
|
+
method="DELETE",
|
|
1734
|
+
timeout=None,
|
|
1735
|
+
failure_message="Failed to delete license -> '{}' for resource -> '{}'".format(
|
|
1736
|
+
license_id, resource_id
|
|
1737
|
+
),
|
|
1738
|
+
parse_request_response=False,
|
|
1739
|
+
)
|
|
1740
|
+
|
|
1741
|
+
if response and response.ok:
|
|
1742
|
+
return True
|
|
1743
|
+
|
|
1744
|
+
return False
|
|
1874
1745
|
|
|
1875
1746
|
# end method definition
|
|
1876
1747
|
|
|
1877
|
-
def
|
|
1878
|
-
|
|
1748
|
+
def assign_user_to_license(
|
|
1749
|
+
self,
|
|
1750
|
+
partition: str,
|
|
1751
|
+
user_id: str,
|
|
1752
|
+
resource_id: str,
|
|
1753
|
+
license_feature: str,
|
|
1754
|
+
license_name: str,
|
|
1755
|
+
license_type: str = "Full",
|
|
1756
|
+
) -> bool:
|
|
1757
|
+
"""Assign an OTDS user to a product license (feature) in OTDS.
|
|
1879
1758
|
|
|
1880
1759
|
Args:
|
|
1881
|
-
|
|
1760
|
+
partition (str): user partition in OTDS, e.g. "Content Server Members"
|
|
1761
|
+
user_id (str): ID of the user (= login name)
|
|
1762
|
+
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
1763
|
+
license_feature (str): name of the license feature
|
|
1764
|
+
license_name (str): name of the license to assign
|
|
1765
|
+
license_type (str, optional): deault is "Full", Extended ECM also has "Occasional"
|
|
1882
1766
|
Returns:
|
|
1883
|
-
|
|
1767
|
+
bool: True if successful or False if the REST call fails or the license is not found
|
|
1884
1768
|
"""
|
|
1885
1769
|
|
|
1886
|
-
|
|
1770
|
+
licenses = self.get_license_for_resource(resource_id)
|
|
1887
1771
|
|
|
1888
|
-
|
|
1772
|
+
for lic in licenses:
|
|
1773
|
+
if lic["_oTLicenseProduct"] == license_name:
|
|
1774
|
+
license_location = lic["id"]
|
|
1775
|
+
|
|
1776
|
+
try:
|
|
1777
|
+
license_location
|
|
1778
|
+
except UnboundLocalError:
|
|
1779
|
+
logger.error(
|
|
1780
|
+
"Cannot find license -> '%s' for resource -> %s",
|
|
1781
|
+
license_name,
|
|
1782
|
+
resource_id,
|
|
1783
|
+
)
|
|
1784
|
+
return False
|
|
1785
|
+
|
|
1786
|
+
user = self.get_user(partition, user_id)
|
|
1787
|
+
if user:
|
|
1788
|
+
user_location = user["location"]
|
|
1789
|
+
else:
|
|
1790
|
+
logger.error("Cannot find location for user -> '%s'", user_id)
|
|
1791
|
+
return False
|
|
1792
|
+
|
|
1793
|
+
license_post_body_json = {
|
|
1794
|
+
"_oTLicenseType": license_type,
|
|
1795
|
+
"_oTLicenseProduct": "users",
|
|
1796
|
+
"name": user_location,
|
|
1797
|
+
"values": [{"name": "counter", "values": [license_feature]}],
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
request_url = self.license_url() + "/object/" + license_location
|
|
1889
1801
|
|
|
1890
|
-
logger.
|
|
1891
|
-
"
|
|
1802
|
+
logger.debug(
|
|
1803
|
+
"Assign license feature -> '%s' of license -> '%s' associated with resource -> '%s' to user -> '%s'; calling -> %s",
|
|
1804
|
+
license_feature,
|
|
1805
|
+
license_location,
|
|
1806
|
+
resource_id,
|
|
1807
|
+
user_id,
|
|
1808
|
+
request_url,
|
|
1892
1809
|
)
|
|
1893
1810
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1811
|
+
response = self.do_request(
|
|
1812
|
+
url=request_url,
|
|
1813
|
+
method="POST",
|
|
1814
|
+
json_data=license_post_body_json,
|
|
1815
|
+
timeout=None,
|
|
1816
|
+
failure_message="Failed to add license feature -> '{}' associated with resource -> '{}' to user -> '{}'".format(
|
|
1817
|
+
license_feature, resource_id, user_id
|
|
1818
|
+
),
|
|
1819
|
+
parse_request_response=False,
|
|
1820
|
+
)
|
|
1821
|
+
|
|
1822
|
+
if response and response.ok:
|
|
1823
|
+
logger.debug(
|
|
1824
|
+
"Added license feature -> '%s' to user -> '%s'",
|
|
1825
|
+
license_feature,
|
|
1826
|
+
user_id,
|
|
1902
1827
|
)
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
elif response.status_code == 401 and retries == 0:
|
|
1907
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1908
|
-
self.authenticate(revalidate=True)
|
|
1909
|
-
retries += 1
|
|
1910
|
-
else:
|
|
1911
|
-
logger.error(
|
|
1912
|
-
"Failed to activate resource -> %s; error -> %s (%s)",
|
|
1913
|
-
resource_id,
|
|
1914
|
-
response.text,
|
|
1915
|
-
response.status_code,
|
|
1916
|
-
)
|
|
1917
|
-
return None
|
|
1828
|
+
return True
|
|
1829
|
+
|
|
1830
|
+
return False
|
|
1918
1831
|
|
|
1919
1832
|
# end method definition
|
|
1920
1833
|
|
|
1921
|
-
def
|
|
1922
|
-
|
|
1834
|
+
def assign_partition_to_license(
|
|
1835
|
+
self,
|
|
1836
|
+
partition_name: str,
|
|
1837
|
+
resource_id: str,
|
|
1838
|
+
license_feature: str,
|
|
1839
|
+
license_name: str,
|
|
1840
|
+
license_type: str = "Full",
|
|
1841
|
+
) -> bool:
|
|
1842
|
+
"""Assign an OTDS partition to a product license (feature).
|
|
1923
1843
|
|
|
1924
1844
|
Args:
|
|
1925
|
-
|
|
1845
|
+
partition_name (str): user partition in OTDS, e.g. "Content Server Members"
|
|
1846
|
+
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
1847
|
+
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
1848
|
+
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
1849
|
+
license_type (str, optional): deault is "Full", Extended ECM also has "Occasional"
|
|
1926
1850
|
Returns:
|
|
1927
|
-
|
|
1851
|
+
bool: True if successful or False if the REST call fails or the license is not found
|
|
1928
1852
|
"""
|
|
1929
1853
|
|
|
1930
|
-
|
|
1854
|
+
licenses = self.get_license_for_resource(resource_id)
|
|
1855
|
+
if not licenses:
|
|
1856
|
+
logger.error(
|
|
1857
|
+
"Resource with ID -> '%s' does not exist or has no licenses",
|
|
1858
|
+
resource_id,
|
|
1859
|
+
)
|
|
1860
|
+
return False
|
|
1931
1861
|
|
|
1932
|
-
|
|
1862
|
+
# licenses have this format:
|
|
1863
|
+
# {
|
|
1864
|
+
# '_oTLicenseType': 'NON-PRODUCTION',
|
|
1865
|
+
# '_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
1866
|
+
# '_oTLicenseResourceName': 'cs',
|
|
1867
|
+
# '_oTLicenseProduct': 'EXTENDED_ECM',
|
|
1868
|
+
# 'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
1869
|
+
# 'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
1870
|
+
# 'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
1871
|
+
# 'description': 'CS license',
|
|
1872
|
+
# 'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
1873
|
+
# }
|
|
1874
|
+
for lic in licenses:
|
|
1875
|
+
if lic["_oTLicenseProduct"] == license_name:
|
|
1876
|
+
license_location = lic["id"]
|
|
1933
1877
|
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1878
|
+
try:
|
|
1879
|
+
license_location
|
|
1880
|
+
except UnboundLocalError:
|
|
1881
|
+
logger.error(
|
|
1882
|
+
"Cannot find license -> %s for resource -> %s",
|
|
1883
|
+
license_name,
|
|
1884
|
+
resource_id,
|
|
1941
1885
|
)
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1886
|
+
return False
|
|
1887
|
+
|
|
1888
|
+
license_post_body_json = {
|
|
1889
|
+
"_oTLicenseType": license_type,
|
|
1890
|
+
"_oTLicenseProduct": "partitions",
|
|
1891
|
+
"name": partition_name,
|
|
1892
|
+
"values": [{"name": "counter", "values": [license_feature]}],
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
request_url = self.license_url() + "/object/" + license_location
|
|
1896
|
+
|
|
1897
|
+
logger.debug(
|
|
1898
|
+
"Assign license feature -> '%s' of license -> '%s' associated with resource -> '%s' to partition -> '%s'; calling -> %s",
|
|
1899
|
+
license_feature,
|
|
1900
|
+
license_location,
|
|
1901
|
+
resource_id,
|
|
1902
|
+
partition_name,
|
|
1903
|
+
request_url,
|
|
1904
|
+
)
|
|
1905
|
+
|
|
1906
|
+
response = self.do_request(
|
|
1907
|
+
url=request_url,
|
|
1908
|
+
method="POST",
|
|
1909
|
+
json_data=license_post_body_json,
|
|
1910
|
+
timeout=None,
|
|
1911
|
+
failure_message="Failed to add license feature -> '{}' associated with resource -> '{}' to partition -> '{}'".format(
|
|
1912
|
+
license_feature, resource_id, partition_name
|
|
1913
|
+
),
|
|
1914
|
+
parse_request_response=False,
|
|
1915
|
+
)
|
|
1916
|
+
|
|
1917
|
+
if response and response.ok:
|
|
1918
|
+
logger.debug(
|
|
1919
|
+
"Added license feature -> '%s' to partition -> '%s'",
|
|
1920
|
+
license_feature,
|
|
1921
|
+
partition_name,
|
|
1922
|
+
)
|
|
1923
|
+
return True
|
|
1924
|
+
|
|
1925
|
+
return False
|
|
1956
1926
|
|
|
1957
1927
|
# end method definition
|
|
1958
1928
|
|
|
1959
|
-
def
|
|
1960
|
-
|
|
1929
|
+
def get_licensed_objects(
|
|
1930
|
+
self,
|
|
1931
|
+
resource_id: str,
|
|
1932
|
+
license_feature: str,
|
|
1933
|
+
license_name: str,
|
|
1934
|
+
) -> dict | None:
|
|
1935
|
+
"""Return the licensed objects (users, groups, partitions) in OTDS for a license + license feature
|
|
1936
|
+
associated with an OTDS resource (like "cs").
|
|
1961
1937
|
|
|
1962
1938
|
Args:
|
|
1963
|
-
|
|
1939
|
+
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
1940
|
+
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
1941
|
+
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
1964
1942
|
Returns:
|
|
1965
|
-
dict:
|
|
1943
|
+
dict: data structure of licensed objects
|
|
1944
|
+
|
|
1945
|
+
Example return value:
|
|
1946
|
+
{
|
|
1947
|
+
'status': 0,
|
|
1948
|
+
'displayString': 'Success',
|
|
1949
|
+
'exceptions': None,
|
|
1950
|
+
'retValue': 0,
|
|
1951
|
+
'listGroupsResults': {'groups': [...], 'actualPageSize': 0, 'nextPageCookie': None, 'requestedPageSize': 250},
|
|
1952
|
+
'listUsersResults': {'users': [...], 'actualPageSize': 53, 'nextPageCookie': None, 'requestedPageSize': 250},
|
|
1953
|
+
'listUserPartitionResult': {'_userPartitions': [...], 'warningMessage': None, 'actualPageSize': 0, 'nextPageCookie': None, 'requestedPageSize': 250},
|
|
1954
|
+
'version': 1
|
|
1955
|
+
}
|
|
1966
1956
|
"""
|
|
1967
1957
|
|
|
1968
|
-
|
|
1958
|
+
licenses = self.get_license_for_resource(resource_id)
|
|
1959
|
+
if not licenses:
|
|
1960
|
+
logger.error(
|
|
1961
|
+
"Resource with ID -> '%s' does not exist or has no licenses",
|
|
1962
|
+
resource_id,
|
|
1963
|
+
)
|
|
1964
|
+
return False
|
|
1969
1965
|
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1966
|
+
# licenses have this format:
|
|
1967
|
+
# {
|
|
1968
|
+
# '_oTLicenseType': 'NON-PRODUCTION',
|
|
1969
|
+
# '_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
1970
|
+
# '_oTLicenseResourceName': 'cs',
|
|
1971
|
+
# '_oTLicenseProduct': 'EXTENDED_ECM',
|
|
1972
|
+
# 'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
1973
|
+
# 'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
1974
|
+
# 'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
1975
|
+
# 'description': 'CS license',
|
|
1976
|
+
# 'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
1977
|
+
# }
|
|
1978
|
+
for lic in licenses:
|
|
1979
|
+
if lic["_oTLicenseProduct"] == license_name:
|
|
1980
|
+
license_location = lic["location"]
|
|
1973
1981
|
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1982
|
+
try:
|
|
1983
|
+
license_location
|
|
1984
|
+
except UnboundLocalError:
|
|
1985
|
+
logger.error(
|
|
1986
|
+
"Cannot find license -> %s for resource -> %s",
|
|
1987
|
+
license_name,
|
|
1988
|
+
resource_id,
|
|
1981
1989
|
)
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1990
|
+
return False
|
|
1991
|
+
|
|
1992
|
+
request_url = (
|
|
1993
|
+
self.license_url()
|
|
1994
|
+
+ "/object/"
|
|
1995
|
+
+ license_location
|
|
1996
|
+
+ "?counter="
|
|
1997
|
+
+ license_feature
|
|
1998
|
+
)
|
|
1999
|
+
|
|
2000
|
+
logger.debug(
|
|
2001
|
+
"Get licensed objects for license -> %s and license feature -> %s associated with resource -> %s; calling -> %s",
|
|
2002
|
+
license_name,
|
|
2003
|
+
license_feature,
|
|
2004
|
+
resource_id,
|
|
2005
|
+
request_url,
|
|
2006
|
+
)
|
|
2007
|
+
|
|
2008
|
+
return self.do_request(
|
|
2009
|
+
url=request_url,
|
|
2010
|
+
method="GET",
|
|
2011
|
+
timeout=None,
|
|
2012
|
+
failure_message="Failed to get licensed objects for license -> '{}' and license feature -> '{}' associated with resource -> '{}'".format(
|
|
2013
|
+
license_name, license_feature, resource_id
|
|
2014
|
+
),
|
|
2015
|
+
)
|
|
1997
2016
|
|
|
1998
2017
|
# end method definition
|
|
1999
2018
|
|
|
2000
|
-
def
|
|
2001
|
-
self,
|
|
2019
|
+
def is_user_licensed(
|
|
2020
|
+
self, user_name: str, resource_id: str, license_feature: str, license_name: str
|
|
2002
2021
|
) -> bool:
|
|
2003
|
-
"""
|
|
2022
|
+
"""Check if a user is licensed for a license and license feature associated with a particular OTDS resource.
|
|
2004
2023
|
|
|
2005
2024
|
Args:
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2025
|
+
user_name (str): login name of the OTDS user
|
|
2026
|
+
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
2027
|
+
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
2028
|
+
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
2029
|
+
|
|
2010
2030
|
Returns:
|
|
2011
|
-
bool: True if
|
|
2012
|
-
False if partition has been not been added (error)
|
|
2031
|
+
bool: True if the user is licensed and False otherwise
|
|
2013
2032
|
"""
|
|
2014
2033
|
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
request_url = "{}/{}/members".format(
|
|
2020
|
-
self.config()["accessRoleUrl"], access_role
|
|
2021
|
-
)
|
|
2022
|
-
|
|
2023
|
-
logger.info(
|
|
2024
|
-
"Add user partition -> %s to access role -> %s; calling -> %s",
|
|
2025
|
-
partition,
|
|
2026
|
-
access_role,
|
|
2027
|
-
request_url,
|
|
2034
|
+
response = self.get_licensed_objects(
|
|
2035
|
+
resource_id=resource_id,
|
|
2036
|
+
license_feature=license_feature,
|
|
2037
|
+
license_name=license_name,
|
|
2028
2038
|
)
|
|
2029
2039
|
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
if
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
"Failed to add partition -> %s to access role -> %s; error -> %s (%s)",
|
|
2048
|
-
partition,
|
|
2049
|
-
access_role,
|
|
2050
|
-
response.text,
|
|
2051
|
-
response.status_code,
|
|
2052
|
-
)
|
|
2053
|
-
return False
|
|
2040
|
+
if not response or not response["listUsersResults"]:
|
|
2041
|
+
return False
|
|
2042
|
+
|
|
2043
|
+
users = response["listUsersResults"]["users"]
|
|
2044
|
+
|
|
2045
|
+
if not users:
|
|
2046
|
+
return False
|
|
2047
|
+
|
|
2048
|
+
user = next(
|
|
2049
|
+
(item for item in users if item["name"] == user_name),
|
|
2050
|
+
None,
|
|
2051
|
+
)
|
|
2052
|
+
|
|
2053
|
+
if user:
|
|
2054
|
+
return True
|
|
2055
|
+
|
|
2056
|
+
return False
|
|
2054
2057
|
|
|
2055
2058
|
# end method definition
|
|
2056
2059
|
|
|
2057
|
-
def
|
|
2058
|
-
self,
|
|
2060
|
+
def is_group_licensed(
|
|
2061
|
+
self, group_name: str, resource_id: str, license_feature: str, license_name: str
|
|
2059
2062
|
) -> bool:
|
|
2060
|
-
"""
|
|
2063
|
+
"""Check if a group is licensed for a license and license feature associated with a particular OTDS resource.
|
|
2061
2064
|
|
|
2062
2065
|
Args:
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2066
|
+
group_name (str): name of the OTDS user group
|
|
2067
|
+
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
2068
|
+
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
2069
|
+
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
2070
|
+
|
|
2067
2071
|
Returns:
|
|
2068
|
-
bool: True if
|
|
2069
|
-
False if user has not been added (error)
|
|
2072
|
+
bool: True if the group is licensed and False otherwise
|
|
2070
2073
|
"""
|
|
2071
2074
|
|
|
2072
|
-
|
|
2073
|
-
|
|
2075
|
+
response = self.get_licensed_objects(
|
|
2076
|
+
resource_id=resource_id,
|
|
2077
|
+
license_feature=license_feature,
|
|
2078
|
+
license_name=license_name,
|
|
2079
|
+
)
|
|
2074
2080
|
|
|
2075
|
-
if not
|
|
2081
|
+
if not response or not response["listGroupsResults"]:
|
|
2076
2082
|
return False
|
|
2077
2083
|
|
|
2078
|
-
|
|
2079
|
-
accessRoleUsers = accessRolesGetResponse["accessRoleMembers"]["users"]
|
|
2080
|
-
for user in accessRoleUsers:
|
|
2081
|
-
if user["displayName"] == user_id:
|
|
2082
|
-
logger.info(
|
|
2083
|
-
"User -> %s already added to access role -> %s",
|
|
2084
|
-
user_id,
|
|
2085
|
-
access_role,
|
|
2086
|
-
)
|
|
2087
|
-
return True
|
|
2088
|
-
|
|
2089
|
-
logger.info(
|
|
2090
|
-
"User -> %s is not yet in access role -> %s - adding...",
|
|
2091
|
-
user_id,
|
|
2092
|
-
access_role,
|
|
2093
|
-
)
|
|
2084
|
+
groups = response["listGroupsResults"]["groups"]
|
|
2094
2085
|
|
|
2095
|
-
|
|
2096
|
-
|
|
2086
|
+
if not groups:
|
|
2087
|
+
return False
|
|
2097
2088
|
|
|
2098
|
-
|
|
2099
|
-
|
|
2089
|
+
group = next(
|
|
2090
|
+
(item for item in groups if item["name"] == group_name),
|
|
2091
|
+
None,
|
|
2100
2092
|
)
|
|
2101
2093
|
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
user_id,
|
|
2105
|
-
access_role,
|
|
2106
|
-
request_url,
|
|
2107
|
-
)
|
|
2094
|
+
if group:
|
|
2095
|
+
return True
|
|
2108
2096
|
|
|
2109
|
-
|
|
2110
|
-
while True:
|
|
2111
|
-
response = requests.post(
|
|
2112
|
-
url=request_url,
|
|
2113
|
-
json=accessRolePostBodyJson,
|
|
2114
|
-
headers=REQUEST_HEADERS,
|
|
2115
|
-
cookies=self.cookie(),
|
|
2116
|
-
timeout=None,
|
|
2117
|
-
)
|
|
2118
|
-
if response.ok:
|
|
2119
|
-
return True
|
|
2120
|
-
elif response.status_code == 401 and retries == 0:
|
|
2121
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
2122
|
-
self.authenticate(revalidate=True)
|
|
2123
|
-
retries += 1
|
|
2124
|
-
else:
|
|
2125
|
-
logger.error(
|
|
2126
|
-
"Failed to add user -> %s to access role -> %s; error -> %s (%s)",
|
|
2127
|
-
user_id,
|
|
2128
|
-
access_role,
|
|
2129
|
-
response.text,
|
|
2130
|
-
response.status_code,
|
|
2131
|
-
)
|
|
2132
|
-
return False
|
|
2097
|
+
return False
|
|
2133
2098
|
|
|
2134
2099
|
# end method definition
|
|
2135
2100
|
|
|
2136
|
-
def
|
|
2137
|
-
self,
|
|
2101
|
+
def is_partition_licensed(
|
|
2102
|
+
self,
|
|
2103
|
+
partition_name: str,
|
|
2104
|
+
resource_id: str,
|
|
2105
|
+
license_feature: str,
|
|
2106
|
+
license_name: str,
|
|
2138
2107
|
) -> bool:
|
|
2139
|
-
"""
|
|
2108
|
+
"""Check if a partition is licensed for a license and license feature associated with a particular OTDS resource.
|
|
2140
2109
|
|
|
2141
2110
|
Args:
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2111
|
+
partition_name (str): name of the OTDS user partition, e.g. "Content Server Members"
|
|
2112
|
+
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
2113
|
+
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
2114
|
+
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
2115
|
+
|
|
2146
2116
|
Returns:
|
|
2147
|
-
bool: True if
|
|
2148
|
-
False if group has been not been added (error)
|
|
2117
|
+
bool: True if the partition is licensed and False otherwise
|
|
2149
2118
|
"""
|
|
2150
2119
|
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2120
|
+
response = self.get_licensed_objects(
|
|
2121
|
+
resource_id=resource_id,
|
|
2122
|
+
license_feature=license_feature,
|
|
2123
|
+
license_name=license_name,
|
|
2124
|
+
)
|
|
2125
|
+
|
|
2126
|
+
if not response or not response["listUserPartitionResult"]:
|
|
2154
2127
|
return False
|
|
2155
2128
|
|
|
2156
|
-
|
|
2157
|
-
accessRoleGroups = accessRolesGetResponse["accessRoleMembers"]["groups"]
|
|
2158
|
-
for accessRoleGroup in accessRoleGroups:
|
|
2159
|
-
if accessRoleGroup["name"] == group:
|
|
2160
|
-
logger.info(
|
|
2161
|
-
"Group -> %s already added to access role -> %s", group, access_role
|
|
2162
|
-
)
|
|
2163
|
-
return True
|
|
2129
|
+
partitions = response["listUserPartitionResult"]["_userPartitions"]
|
|
2164
2130
|
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2131
|
+
if not partitions:
|
|
2132
|
+
return False
|
|
2133
|
+
|
|
2134
|
+
partition = next(
|
|
2135
|
+
(item for item in partitions if item["name"] == partition_name),
|
|
2136
|
+
None,
|
|
2169
2137
|
)
|
|
2170
2138
|
|
|
2171
|
-
|
|
2172
|
-
|
|
2139
|
+
if partition:
|
|
2140
|
+
return True
|
|
2173
2141
|
|
|
2174
|
-
|
|
2175
|
-
self.config()["accessRoleUrl"], access_role
|
|
2176
|
-
)
|
|
2142
|
+
return False
|
|
2177
2143
|
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2144
|
+
# end method definition
|
|
2145
|
+
|
|
2146
|
+
def import_synchronized_partition_members(self, name: str) -> dict:
|
|
2147
|
+
"""Import users and groups to partition
|
|
2148
|
+
|
|
2149
|
+
Args:
|
|
2150
|
+
name (str): name of the partition in which users need to be imported
|
|
2151
|
+
Returns:
|
|
2152
|
+
dict: Request response or None if the creation fails.
|
|
2153
|
+
"""
|
|
2154
|
+
command = {"command": "import"}
|
|
2155
|
+
request_url = self.synchronized_partition_url() + f'/{name}/command'
|
|
2156
|
+
logger.debug(
|
|
2157
|
+
"Importing users and groups in to partition -> %s; calling -> %s",
|
|
2158
|
+
name,
|
|
2182
2159
|
request_url,
|
|
2183
2160
|
)
|
|
2184
|
-
|
|
2185
2161
|
retries = 0
|
|
2186
2162
|
while True:
|
|
2187
2163
|
response = requests.post(
|
|
2188
2164
|
url=request_url,
|
|
2189
|
-
json=
|
|
2165
|
+
json=command,
|
|
2190
2166
|
headers=REQUEST_HEADERS,
|
|
2191
2167
|
cookies=self.cookie(),
|
|
2192
2168
|
timeout=None,
|
|
2193
2169
|
)
|
|
2194
|
-
if response.
|
|
2170
|
+
if response.status_code == 204:
|
|
2195
2171
|
return True
|
|
2172
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
2196
2173
|
elif response.status_code == 401 and retries == 0:
|
|
2197
|
-
logger.
|
|
2174
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2198
2175
|
self.authenticate(revalidate=True)
|
|
2199
2176
|
retries += 1
|
|
2200
2177
|
else:
|
|
2201
2178
|
logger.error(
|
|
2202
|
-
"Failed to
|
|
2203
|
-
|
|
2204
|
-
access_role,
|
|
2179
|
+
"Failed to Import users and groups to synchronized partition -> %s; error -> %s (%s)",
|
|
2180
|
+
name,
|
|
2205
2181
|
response.text,
|
|
2206
2182
|
response.status_code,
|
|
2207
2183
|
)
|
|
2208
|
-
return
|
|
2209
|
-
|
|
2210
|
-
# end method definition
|
|
2211
|
-
|
|
2212
|
-
def
|
|
2213
|
-
|
|
2214
|
-
) -> dict | None:
|
|
2215
|
-
"""Update some attributes of an existing OTDS Access Role
|
|
2184
|
+
return None
|
|
2185
|
+
|
|
2186
|
+
# end of method definition
|
|
2187
|
+
|
|
2188
|
+
def add_synchronized_partition(self, name: str, description: str, data: str) -> dict:
|
|
2189
|
+
"""Add a new synchronized partition to OTDS
|
|
2216
2190
|
|
|
2217
2191
|
Args:
|
|
2218
|
-
name (str): name of the
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
[{name: "pushAllGroups", values: ["True"]}]
|
|
2192
|
+
name (str): name of the new partition
|
|
2193
|
+
description (str): description of the new partition
|
|
2194
|
+
data (dict): data for creating synchronized partition
|
|
2222
2195
|
Returns:
|
|
2223
|
-
dict: Request response
|
|
2196
|
+
dict: Request response or None if the creation fails.
|
|
2224
2197
|
"""
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
request_url =
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
"Update access role -> %s with attributes -> %s; calling -> %s",
|
|
2198
|
+
synchronizedPartitionPostBodyJson = {
|
|
2199
|
+
"ipConnectionParameter": [
|
|
2200
|
+
],
|
|
2201
|
+
"ipAuthentication": {
|
|
2202
|
+
},
|
|
2203
|
+
"objectClassNameMapping": [
|
|
2204
|
+
|
|
2205
|
+
],
|
|
2206
|
+
"basicInfo": {
|
|
2207
|
+
},
|
|
2208
|
+
"basicAttributes": []
|
|
2209
|
+
}
|
|
2210
|
+
synchronizedPartitionPostBodyJson.update(data)
|
|
2211
|
+
request_url = self.synchronized_partition_url()
|
|
2212
|
+
logger.debug(
|
|
2213
|
+
"Adding synchronized partition -> %s (%s); calling -> %s",
|
|
2242
2214
|
name,
|
|
2243
|
-
|
|
2215
|
+
description,
|
|
2244
2216
|
request_url,
|
|
2245
2217
|
)
|
|
2246
|
-
|
|
2218
|
+
synchronizedPartitionPostBodyJson["ipAuthentication"]["bindPassword"] = self.config()["bindPassword"]
|
|
2247
2219
|
retries = 0
|
|
2248
2220
|
while True:
|
|
2249
|
-
response = requests.
|
|
2221
|
+
response = requests.post(
|
|
2250
2222
|
url=request_url,
|
|
2251
|
-
json=
|
|
2223
|
+
json=synchronizedPartitionPostBodyJson,
|
|
2252
2224
|
headers=REQUEST_HEADERS,
|
|
2253
2225
|
cookies=self.cookie(),
|
|
2254
2226
|
timeout=None,
|
|
@@ -2257,19 +2229,19 @@ class OTDS:
|
|
|
2257
2229
|
return self.parse_request_response(response)
|
|
2258
2230
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2259
2231
|
elif response.status_code == 401 and retries == 0:
|
|
2260
|
-
logger.
|
|
2232
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2261
2233
|
self.authenticate(revalidate=True)
|
|
2262
2234
|
retries += 1
|
|
2263
2235
|
else:
|
|
2264
2236
|
logger.error(
|
|
2265
|
-
"Failed to
|
|
2237
|
+
"Failed to add synchronized partition -> %s; error -> %s (%s)",
|
|
2266
2238
|
name,
|
|
2267
2239
|
response.text,
|
|
2268
2240
|
response.status_code,
|
|
2269
2241
|
)
|
|
2270
2242
|
return None
|
|
2271
|
-
|
|
2272
|
-
# end method definition
|
|
2243
|
+
|
|
2244
|
+
# end of method definition
|
|
2273
2245
|
|
|
2274
2246
|
def add_system_attribute(
|
|
2275
2247
|
self, name: str, value: str, description: str = ""
|
|
@@ -2284,7 +2256,7 @@ class OTDS:
|
|
|
2284
2256
|
dict: Request response (dictionary) or None if the REST call fails.
|
|
2285
2257
|
"""
|
|
2286
2258
|
|
|
2287
|
-
|
|
2259
|
+
system_attribute_post_body_json = {
|
|
2288
2260
|
"name": name,
|
|
2289
2261
|
"value": value,
|
|
2290
2262
|
"friendlyName": description,
|
|
@@ -2293,46 +2265,30 @@ class OTDS:
|
|
|
2293
2265
|
request_url = "{}/system_attributes".format(self.config()["systemConfigUrl"])
|
|
2294
2266
|
|
|
2295
2267
|
if description:
|
|
2296
|
-
logger.
|
|
2297
|
-
"Add system attribute -> %s (%s) with value -> %s; calling -> %s",
|
|
2268
|
+
logger.debug(
|
|
2269
|
+
"Add system attribute -> '%s' ('%s') with value -> %s; calling -> %s",
|
|
2298
2270
|
name,
|
|
2299
2271
|
description,
|
|
2300
2272
|
value,
|
|
2301
2273
|
request_url,
|
|
2302
2274
|
)
|
|
2303
2275
|
else:
|
|
2304
|
-
logger.
|
|
2305
|
-
"Add system attribute -> %s with value -> %s; calling -> %s",
|
|
2276
|
+
logger.debug(
|
|
2277
|
+
"Add system attribute -> '%s' with value -> %s; calling -> %s",
|
|
2306
2278
|
name,
|
|
2307
2279
|
value,
|
|
2308
2280
|
request_url,
|
|
2309
2281
|
)
|
|
2310
2282
|
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
if response.ok:
|
|
2321
|
-
return self.parse_request_response(response)
|
|
2322
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2323
|
-
elif response.status_code == 401 and retries == 0:
|
|
2324
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
2325
|
-
self.authenticate(revalidate=True)
|
|
2326
|
-
retries += 1
|
|
2327
|
-
else:
|
|
2328
|
-
logger.error(
|
|
2329
|
-
"Failed to add system attribute -> %s with value -> %s; error -> %s (%s)",
|
|
2330
|
-
name,
|
|
2331
|
-
value,
|
|
2332
|
-
response.text,
|
|
2333
|
-
response.status_code,
|
|
2334
|
-
)
|
|
2335
|
-
return None
|
|
2283
|
+
return self.do_request(
|
|
2284
|
+
url=request_url,
|
|
2285
|
+
method="POST",
|
|
2286
|
+
json_data=system_attribute_post_body_json,
|
|
2287
|
+
timeout=None,
|
|
2288
|
+
failure_message="Failed to add system attribute -> '{}' with value -> '{}'".format(
|
|
2289
|
+
name, value
|
|
2290
|
+
),
|
|
2291
|
+
)
|
|
2336
2292
|
|
|
2337
2293
|
# end method definition
|
|
2338
2294
|
|
|
@@ -2347,30 +2303,14 @@ class OTDS:
|
|
|
2347
2303
|
|
|
2348
2304
|
request_url = "{}/whitelist".format(self.config()["systemConfigUrl"])
|
|
2349
2305
|
|
|
2350
|
-
logger.
|
|
2306
|
+
logger.debug("Get trusted sites; calling -> %s", request_url)
|
|
2351
2307
|
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
timeout=None,
|
|
2359
|
-
)
|
|
2360
|
-
if response.ok:
|
|
2361
|
-
return self.parse_request_response(response)
|
|
2362
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2363
|
-
elif response.status_code == 401 and retries == 0:
|
|
2364
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
2365
|
-
self.authenticate(revalidate=True)
|
|
2366
|
-
retries += 1
|
|
2367
|
-
else:
|
|
2368
|
-
logger.error(
|
|
2369
|
-
"Failed to retrieve trusted sites; error -> %s (%s)",
|
|
2370
|
-
response.text,
|
|
2371
|
-
response.status_code,
|
|
2372
|
-
)
|
|
2373
|
-
return None
|
|
2308
|
+
return self.do_request(
|
|
2309
|
+
url=request_url,
|
|
2310
|
+
method="GET",
|
|
2311
|
+
timeout=None,
|
|
2312
|
+
failure_message="Failed to get trusted sites",
|
|
2313
|
+
)
|
|
2374
2314
|
|
|
2375
2315
|
# end method definition
|
|
2376
2316
|
|
|
@@ -2383,38 +2323,36 @@ class OTDS:
|
|
|
2383
2323
|
dict: Request response or None if the REST call fails.
|
|
2384
2324
|
"""
|
|
2385
2325
|
|
|
2386
|
-
|
|
2326
|
+
trusted_site_post_body_json = {"stringList": [trusted_site]}
|
|
2387
2327
|
|
|
2388
2328
|
# we need to first retrieve the existing sites and then
|
|
2389
2329
|
# append the new one:
|
|
2390
|
-
|
|
2330
|
+
existing_trusted_sites = self.get_trusted_sites()
|
|
2391
2331
|
|
|
2392
|
-
if
|
|
2393
|
-
|
|
2394
|
-
|
|
2332
|
+
if existing_trusted_sites:
|
|
2333
|
+
trusted_site_post_body_json["stringList"].extend(
|
|
2334
|
+
existing_trusted_sites["stringList"]
|
|
2395
2335
|
)
|
|
2396
2336
|
|
|
2397
2337
|
request_url = "{}/whitelist".format(self.config()["systemConfigUrl"])
|
|
2398
2338
|
|
|
2399
|
-
logger.
|
|
2339
|
+
logger.debug(
|
|
2340
|
+
"Add trusted site -> '%s'; calling -> %s", trusted_site, request_url
|
|
2341
|
+
)
|
|
2400
2342
|
|
|
2401
|
-
response =
|
|
2343
|
+
response = self.do_request(
|
|
2402
2344
|
url=request_url,
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
cookies=self.cookie(),
|
|
2345
|
+
method="PUT",
|
|
2346
|
+
json_data=trusted_site_post_body_json,
|
|
2406
2347
|
timeout=None,
|
|
2348
|
+
failure_message="Failed to add trusted site -> '{}'".format(trusted_site),
|
|
2349
|
+
parse_request_response=False, # don't parse it!
|
|
2407
2350
|
)
|
|
2351
|
+
|
|
2408
2352
|
if not response.ok:
|
|
2409
|
-
logger.error(
|
|
2410
|
-
"Failed to add trusted site -> %s; error -> %s (%s)",
|
|
2411
|
-
trusted_site,
|
|
2412
|
-
response.text,
|
|
2413
|
-
response.status_code,
|
|
2414
|
-
)
|
|
2415
2353
|
return None
|
|
2416
2354
|
|
|
2417
|
-
return response
|
|
2355
|
+
return response
|
|
2418
2356
|
|
|
2419
2357
|
# end method definition
|
|
2420
2358
|
|
|
@@ -2427,7 +2365,7 @@ class OTDS:
|
|
|
2427
2365
|
Request response (json) or None if the REST call fails.
|
|
2428
2366
|
"""
|
|
2429
2367
|
|
|
2430
|
-
|
|
2368
|
+
audit_put_body_json = {
|
|
2431
2369
|
"daysToKeep": "7",
|
|
2432
2370
|
"enabled": "true",
|
|
2433
2371
|
"auditTo": "DATABASE",
|
|
@@ -2484,22 +2422,16 @@ class OTDS:
|
|
|
2484
2422
|
|
|
2485
2423
|
request_url = "{}/audit".format(self.config()["systemConfigUrl"])
|
|
2486
2424
|
|
|
2487
|
-
logger.
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
url=request_url,
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
logger.error(
|
|
2498
|
-
"Failed to enable audit; error -> %s (%s)",
|
|
2499
|
-
response.text,
|
|
2500
|
-
response.status_code,
|
|
2501
|
-
)
|
|
2502
|
-
return response
|
|
2425
|
+
logger.debug("Enable audit; calling -> %s", request_url)
|
|
2426
|
+
|
|
2427
|
+
return self.do_request(
|
|
2428
|
+
url=request_url,
|
|
2429
|
+
method="PUT",
|
|
2430
|
+
json_data=audit_put_body_json,
|
|
2431
|
+
timeout=None,
|
|
2432
|
+
failure_message="Failed to enable audit",
|
|
2433
|
+
parse_request_response=False,
|
|
2434
|
+
)
|
|
2503
2435
|
|
|
2504
2436
|
# end method definition
|
|
2505
2437
|
|
|
@@ -2583,7 +2515,7 @@ class OTDS:
|
|
|
2583
2515
|
if default_scopes is None:
|
|
2584
2516
|
default_scopes = []
|
|
2585
2517
|
|
|
2586
|
-
|
|
2518
|
+
oauth_client_post_body_json = {
|
|
2587
2519
|
"id": client_id,
|
|
2588
2520
|
"description": description,
|
|
2589
2521
|
"redirectURLs": redirect_urls,
|
|
@@ -2601,41 +2533,24 @@ class OTDS:
|
|
|
2601
2533
|
|
|
2602
2534
|
# Do we have a predefined client secret?
|
|
2603
2535
|
if secret:
|
|
2604
|
-
|
|
2536
|
+
oauth_client_post_body_json["secret"] = secret
|
|
2605
2537
|
|
|
2606
2538
|
request_url = self.oauth_client_url()
|
|
2607
2539
|
|
|
2608
|
-
logger.
|
|
2609
|
-
"Adding oauth client -> %s (%s); calling -> %s",
|
|
2540
|
+
logger.debug(
|
|
2541
|
+
"Adding oauth client -> '%s' (%s); calling -> %s",
|
|
2610
2542
|
description,
|
|
2611
2543
|
client_id,
|
|
2612
2544
|
request_url,
|
|
2613
2545
|
)
|
|
2614
2546
|
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
timeout=None,
|
|
2623
|
-
)
|
|
2624
|
-
if response.ok:
|
|
2625
|
-
return self.parse_request_response(response)
|
|
2626
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2627
|
-
elif response.status_code == 401 and retries == 0:
|
|
2628
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
2629
|
-
self.authenticate(revalidate=True)
|
|
2630
|
-
retries += 1
|
|
2631
|
-
else:
|
|
2632
|
-
logger.error(
|
|
2633
|
-
"Failed to add OAuth client -> %s; error -> %s (%s)",
|
|
2634
|
-
client_id,
|
|
2635
|
-
response.text,
|
|
2636
|
-
response.status_code,
|
|
2637
|
-
)
|
|
2638
|
-
return None
|
|
2547
|
+
return self.do_request(
|
|
2548
|
+
url=request_url,
|
|
2549
|
+
method="POST",
|
|
2550
|
+
json_data=oauth_client_post_body_json,
|
|
2551
|
+
timeout=None,
|
|
2552
|
+
failure_message="Failed to add OAuth client -> {}".format(client_id),
|
|
2553
|
+
)
|
|
2639
2554
|
|
|
2640
2555
|
# end method definition
|
|
2641
2556
|
|
|
@@ -2651,32 +2566,15 @@ class OTDS:
|
|
|
2651
2566
|
|
|
2652
2567
|
request_url = "{}/{}".format(self.oauth_client_url(), client_id)
|
|
2653
2568
|
|
|
2654
|
-
logger.
|
|
2569
|
+
logger.debug("Get oauth client -> '%s'; calling -> %s", client_id, request_url)
|
|
2655
2570
|
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
)
|
|
2664
|
-
if response.ok:
|
|
2665
|
-
return self.parse_request_response(response)
|
|
2666
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2667
|
-
elif response.status_code == 401 and retries == 0:
|
|
2668
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
2669
|
-
self.authenticate(revalidate=True)
|
|
2670
|
-
retries += 1
|
|
2671
|
-
else:
|
|
2672
|
-
if show_error:
|
|
2673
|
-
logger.error(
|
|
2674
|
-
"Failed to get oauth client -> %s; error -> %s (%s)",
|
|
2675
|
-
client_id,
|
|
2676
|
-
response.text,
|
|
2677
|
-
response.status_code,
|
|
2678
|
-
)
|
|
2679
|
-
return None
|
|
2571
|
+
return self.do_request(
|
|
2572
|
+
url=request_url,
|
|
2573
|
+
method="GET",
|
|
2574
|
+
timeout=None,
|
|
2575
|
+
failure_message="Failed to get oauth client -> '{}'".format(client_id),
|
|
2576
|
+
show_error=show_error,
|
|
2577
|
+
)
|
|
2680
2578
|
|
|
2681
2579
|
# end method definition
|
|
2682
2580
|
|
|
@@ -2692,41 +2590,24 @@ class OTDS:
|
|
|
2692
2590
|
dict: Request response (json) or None if the REST call fails.
|
|
2693
2591
|
"""
|
|
2694
2592
|
|
|
2695
|
-
|
|
2593
|
+
oauth_client_patch_body_json = updates
|
|
2696
2594
|
|
|
2697
2595
|
request_url = "{}/{}".format(self.oauth_client_url(), client_id)
|
|
2698
2596
|
|
|
2699
|
-
logger.
|
|
2700
|
-
"Update OAuth client -> %s with -> %s; calling -> %s",
|
|
2597
|
+
logger.debug(
|
|
2598
|
+
"Update OAuth client -> '%s' with -> %s; calling -> %s",
|
|
2701
2599
|
client_id,
|
|
2702
|
-
updates,
|
|
2600
|
+
str(updates),
|
|
2703
2601
|
request_url,
|
|
2704
2602
|
)
|
|
2705
2603
|
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
timeout=None,
|
|
2714
|
-
)
|
|
2715
|
-
if response.ok:
|
|
2716
|
-
return self.parse_request_response(response)
|
|
2717
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2718
|
-
elif response.status_code == 401 and retries == 0:
|
|
2719
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
2720
|
-
self.authenticate(revalidate=True)
|
|
2721
|
-
retries += 1
|
|
2722
|
-
else:
|
|
2723
|
-
logger.error(
|
|
2724
|
-
"Failed to update OAuth client -> %s; error -> %s (%s)",
|
|
2725
|
-
client_id,
|
|
2726
|
-
response.text,
|
|
2727
|
-
response.status_code,
|
|
2728
|
-
)
|
|
2729
|
-
return None
|
|
2604
|
+
return self.do_request(
|
|
2605
|
+
url=request_url,
|
|
2606
|
+
method="PATCH",
|
|
2607
|
+
json_data=oauth_client_patch_body_json,
|
|
2608
|
+
timeout=None,
|
|
2609
|
+
failure_message="Failed to update OAuth client -> '{}'".format(client_id),
|
|
2610
|
+
)
|
|
2730
2611
|
|
|
2731
2612
|
# end method definition
|
|
2732
2613
|
|
|
@@ -2741,40 +2622,25 @@ class OTDS:
|
|
|
2741
2622
|
|
|
2742
2623
|
request_url = self.config()["accessRoleUrl"] + "/" + access_role_name
|
|
2743
2624
|
|
|
2744
|
-
logger.
|
|
2745
|
-
"Get access role -> %s; calling -> %s", access_role_name, request_url
|
|
2625
|
+
logger.debug(
|
|
2626
|
+
"Get access role -> '%s'; calling -> %s", access_role_name, request_url
|
|
2746
2627
|
)
|
|
2747
2628
|
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
break
|
|
2759
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2760
|
-
elif response.status_code == 401 and retries == 0:
|
|
2761
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
2762
|
-
self.authenticate(revalidate=True)
|
|
2763
|
-
retries += 1
|
|
2764
|
-
else:
|
|
2765
|
-
logger.error(
|
|
2766
|
-
"Failed to retrieve role -> %s; url -> %s : error -> %s (%s)",
|
|
2767
|
-
access_role_name,
|
|
2768
|
-
request_url,
|
|
2769
|
-
response.text,
|
|
2770
|
-
response.status_code,
|
|
2771
|
-
)
|
|
2772
|
-
return None
|
|
2629
|
+
access_role = self.do_request(
|
|
2630
|
+
url=request_url,
|
|
2631
|
+
method="GET",
|
|
2632
|
+
timeout=None,
|
|
2633
|
+
failure_message="Failed to retrieve access role -> '{}'".format(
|
|
2634
|
+
access_role_name
|
|
2635
|
+
),
|
|
2636
|
+
)
|
|
2637
|
+
if not access_role:
|
|
2638
|
+
return None
|
|
2773
2639
|
|
|
2774
2640
|
# Checking if OAuthClients partition already added to access role
|
|
2775
|
-
|
|
2776
|
-
for
|
|
2777
|
-
if
|
|
2641
|
+
user_partitions = access_role["accessRoleMembers"]["userPartitions"]
|
|
2642
|
+
for user_partition in user_partitions:
|
|
2643
|
+
if user_partition["userPartition"] == "OAuthClients":
|
|
2778
2644
|
logger.error(
|
|
2779
2645
|
"OAuthClients partition already added to role -> %s",
|
|
2780
2646
|
access_role_name,
|
|
@@ -2784,58 +2650,42 @@ class OTDS:
|
|
|
2784
2650
|
# Getting location info for the OAuthClients partition
|
|
2785
2651
|
# so it can be added to access roles json
|
|
2786
2652
|
request_url = self.config()["partitionsUrl"] + "/OAuthClients"
|
|
2787
|
-
|
|
2653
|
+
|
|
2654
|
+
response = self.do_request(
|
|
2788
2655
|
url=request_url,
|
|
2789
|
-
|
|
2790
|
-
cookies=self.cookie(),
|
|
2656
|
+
method="GET",
|
|
2791
2657
|
timeout=None,
|
|
2658
|
+
failure_message="Failed to get partition info for OAuthClients for role -> '{}'".format(
|
|
2659
|
+
access_role_name
|
|
2660
|
+
),
|
|
2792
2661
|
)
|
|
2793
|
-
if
|
|
2794
|
-
response_dict = self.parse_request_response(partitionsResponse)
|
|
2795
|
-
if not response_dict:
|
|
2796
|
-
return None
|
|
2797
|
-
oauthClientLocation = response_dict["location"]
|
|
2798
|
-
else:
|
|
2799
|
-
logger.error(
|
|
2800
|
-
"Failed to get partition info for OAuthClients; url -> %s : error -> %s (%s)",
|
|
2801
|
-
request_url,
|
|
2802
|
-
partitionsResponse.text,
|
|
2803
|
-
response.status_code,
|
|
2804
|
-
)
|
|
2662
|
+
if not response:
|
|
2805
2663
|
return None
|
|
2806
2664
|
|
|
2665
|
+
oauth_client_location = response["location"]
|
|
2666
|
+
|
|
2807
2667
|
# adding OAuthClients info to acess roles organizational units
|
|
2808
|
-
|
|
2809
|
-
"location":
|
|
2810
|
-
"name":
|
|
2668
|
+
oauth_clients_ou_block = {
|
|
2669
|
+
"location": oauth_client_location,
|
|
2670
|
+
"name": oauth_client_location,
|
|
2811
2671
|
"userPartition": None,
|
|
2812
2672
|
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2673
|
+
access_role["accessRoleMembers"]["organizationalUnits"].append(
|
|
2674
|
+
oauth_clients_ou_block
|
|
2815
2675
|
)
|
|
2816
2676
|
|
|
2817
|
-
|
|
2677
|
+
return self.do_request(
|
|
2818
2678
|
url=request_url,
|
|
2819
|
-
|
|
2820
|
-
headers=REQUEST_HEADERS,
|
|
2821
|
-
cookies=self.cookie(),
|
|
2679
|
+
method="PUT",
|
|
2822
2680
|
timeout=None,
|
|
2681
|
+
warning_message="Failed to add OAuthClients to access role -> '{}'".format(
|
|
2682
|
+
access_role_name
|
|
2683
|
+
),
|
|
2684
|
+
show_error=False,
|
|
2685
|
+
show_warning=True,
|
|
2686
|
+
parse_request_response=False,
|
|
2823
2687
|
)
|
|
2824
2688
|
|
|
2825
|
-
if response.ok:
|
|
2826
|
-
logger.info(
|
|
2827
|
-
"OauthClients partition successfully added to access role -> %s",
|
|
2828
|
-
access_role_name,
|
|
2829
|
-
)
|
|
2830
|
-
else:
|
|
2831
|
-
logger.warning(
|
|
2832
|
-
"Status code of -> %s returned attempting to add OAuthClients to access role -> %s: error -> %s",
|
|
2833
|
-
response.status_code,
|
|
2834
|
-
access_role_name,
|
|
2835
|
-
response.text,
|
|
2836
|
-
)
|
|
2837
|
-
return response
|
|
2838
|
-
|
|
2839
2689
|
# end method definition
|
|
2840
2690
|
|
|
2841
2691
|
def get_access_token(self, client_id: str, client_secret: str) -> str | None:
|
|
@@ -2925,34 +2775,17 @@ class OTDS:
|
|
|
2925
2775
|
|
|
2926
2776
|
request_url = "{}/{}".format(self.auth_handler_url(), name)
|
|
2927
2777
|
|
|
2928
|
-
logger.
|
|
2929
|
-
"Getting authentication handler -> %s; calling -> %s", name, request_url
|
|
2778
|
+
logger.debug(
|
|
2779
|
+
"Getting authentication handler -> '%s'; calling -> %s", name, request_url
|
|
2930
2780
|
)
|
|
2931
2781
|
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
)
|
|
2940
|
-
if response.ok:
|
|
2941
|
-
return self.parse_request_response(response)
|
|
2942
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2943
|
-
elif response.status_code == 401 and retries == 0:
|
|
2944
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
2945
|
-
self.authenticate(revalidate=True)
|
|
2946
|
-
retries += 1
|
|
2947
|
-
else:
|
|
2948
|
-
if show_error:
|
|
2949
|
-
logger.error(
|
|
2950
|
-
"Failed to get authentication handler -> %s; warning -> %s (%s)",
|
|
2951
|
-
name,
|
|
2952
|
-
response.text,
|
|
2953
|
-
response.status_code,
|
|
2954
|
-
)
|
|
2955
|
-
return None
|
|
2782
|
+
return self.do_request(
|
|
2783
|
+
url=request_url,
|
|
2784
|
+
method="GET",
|
|
2785
|
+
timeout=None,
|
|
2786
|
+
failure_message="Failed to get authentication handler -> '{}'".format(name),
|
|
2787
|
+
show_error=show_error,
|
|
2788
|
+
)
|
|
2956
2789
|
|
|
2957
2790
|
# end method definition
|
|
2958
2791
|
|
|
@@ -2995,7 +2828,7 @@ class OTDS:
|
|
|
2995
2828
|
if auth_principal_attributes is None:
|
|
2996
2829
|
auth_principal_attributes = ["oTExternalID1", "oTUserID1"]
|
|
2997
2830
|
|
|
2998
|
-
|
|
2831
|
+
auth_handler_post_body_json = {
|
|
2999
2832
|
"_name": name,
|
|
3000
2833
|
"_description": description,
|
|
3001
2834
|
"_class": "com.opentext.otds.as.drivers.saml.SAML2Handler",
|
|
@@ -3005,107 +2838,292 @@ class OTDS:
|
|
|
3005
2838
|
"_scope": scope,
|
|
3006
2839
|
"_properties": [
|
|
3007
2840
|
{
|
|
3008
|
-
"_key": "com.opentext.otds.as.drivers.saml.provider_name",
|
|
3009
|
-
"_name": "Identity Provider (IdP) Name",
|
|
3010
|
-
"_description": "The name of the identity provider. This should be a single word since it will be part of the metadata URL.",
|
|
3011
|
-
"_value": provider_name,
|
|
2841
|
+
"_key": "com.opentext.otds.as.drivers.saml.provider_name",
|
|
2842
|
+
"_name": "Identity Provider (IdP) Name",
|
|
2843
|
+
"_description": "The name of the identity provider. This should be a single word since it will be part of the metadata URL.",
|
|
2844
|
+
"_value": provider_name,
|
|
2845
|
+
},
|
|
2846
|
+
{
|
|
2847
|
+
"_key": "com.opentext.otds.as.drivers.saml.provider_metadata_description",
|
|
2848
|
+
"_name": "IdP Metadata URL",
|
|
2849
|
+
"_description": "The URL for the IdP's federation metadata. The metadata will be automatically updated by OTDS daily at midnight.",
|
|
2850
|
+
"_value": saml_url,
|
|
2851
|
+
},
|
|
2852
|
+
{
|
|
2853
|
+
"_key": "com.opentext.otds.as.drivers.saml.provider_nameid_format",
|
|
2854
|
+
"_name": "IdP NameID Format",
|
|
2855
|
+
"_description": "Specifies which NameID format supported by the identity provider contains the desired user identifier. The value in this identifier must correspond to the value of the user attribute specified for the authentication principal attribute. This value is usually set to urn:oasis:names:tc:SAML:2.0:nameid-format:persistent or urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress. Please ensure this is consistent with the identity provider's configuration.",
|
|
2856
|
+
"_value": nameid_format,
|
|
2857
|
+
},
|
|
2858
|
+
{
|
|
2859
|
+
"_key": "com.opentext.otds.as.drivers.saml._impersonator_claim",
|
|
2860
|
+
"_name": "Claim for impersonating user",
|
|
2861
|
+
"_description": "A claim that contains the ID of the actor/impersonator for the user identified by NameID. It must be in the same format as NameID.",
|
|
2862
|
+
"_value": "loggedinuserid",
|
|
2863
|
+
},
|
|
2864
|
+
{
|
|
2865
|
+
"_key": "com.opentext.otds.as.drivers.saml.sp_url",
|
|
2866
|
+
"_name": "OTDS SP Endpoint",
|
|
2867
|
+
"_description": "Specifies the service provider URL that will be used to identify OTDS to the identity provider. If not specified, the URL will be taken from the request. This generally needs to be configured for environments in which OTDS is behind a reverse-proxy.",
|
|
2868
|
+
"_value": otds_sp_endpoint,
|
|
2869
|
+
},
|
|
2870
|
+
{
|
|
2871
|
+
"_key": "com.opentext.otds.as.drivers.saml.enable_sp_sso",
|
|
2872
|
+
"_name": "Active By Default",
|
|
2873
|
+
"_description": "Whether to activate this handler for any request to the OTDS login page. If true, any login request to the OTDS login page will be redirected to the IdP. If false, the user has to select the provider on the login page.",
|
|
2874
|
+
"_value": active_by_default,
|
|
2875
|
+
},
|
|
2876
|
+
{
|
|
2877
|
+
"_key": "com.opentext.otds.as.drivers.saml._signature_alg",
|
|
2878
|
+
"_name": "XML Signature Algorithm",
|
|
2879
|
+
"_description": "Only relevant when certificate and private key are configured. Default is http://www.w3.org/2000/09/xmldsig#rsa-sha1. Valid values are defined at http://www.w3.org/TR/xmldsig-core1/#sec-AlgID.",
|
|
2880
|
+
"_value": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
|
2881
|
+
},
|
|
2882
|
+
{
|
|
2883
|
+
"_key": "com.opentext.otds.as.drivers.saml.use_acs_url",
|
|
2884
|
+
"_name": "Use AssertionConsumerServiceURL",
|
|
2885
|
+
"_description": "Set to true to have the SAML AuthnRequest use AssertionConsumerServiceURL instead of AssertionConsumerServiceIndex",
|
|
2886
|
+
"_value": "true",
|
|
2887
|
+
},
|
|
2888
|
+
{
|
|
2889
|
+
"_key": "com.opentext.otds.as.drivers.saml.grace_period",
|
|
2890
|
+
"_name": "Grace Period",
|
|
2891
|
+
"_description": 'Specifies the number of minutes to allow for "NotBefore" and "NotOnOrAfter" fields when validating assertions in order to account for time difference between the identity provider and this service provider.',
|
|
2892
|
+
"_value": "5",
|
|
2893
|
+
},
|
|
2894
|
+
{
|
|
2895
|
+
"_key": "com.opentext.otds.as.drivers.saml.auth_request_binding",
|
|
2896
|
+
"_name": "Auth Request Binding",
|
|
2897
|
+
"_description": "Specifies the preferred SAML binding to use for sending the AuthnRequest, provided it is supported by the identity provider.",
|
|
2898
|
+
"_value": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
2899
|
+
},
|
|
2900
|
+
{
|
|
2901
|
+
"_key": "com.opentext.otds.as.drivers.saml.auth_response_binding",
|
|
2902
|
+
"_name": "Auth Response Binding",
|
|
2903
|
+
"_description": "Specifies the SAML binding to use for the response to an AuthnRequest",
|
|
2904
|
+
"_value": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
2905
|
+
},
|
|
2906
|
+
{
|
|
2907
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim1",
|
|
2908
|
+
"_name": "Claim 1",
|
|
2909
|
+
"_description": "SAML attribute/claim that should be mapped to an OTDS user attribute. This value is case sensitive. Note that mapped claims are only relevant if the corresponding account is auto-provisioned in OTDS. See the Administration Guide for details.",
|
|
2910
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
|
|
2911
|
+
},
|
|
2912
|
+
{
|
|
2913
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute1",
|
|
2914
|
+
"_name": "OTDS Attribute 1",
|
|
2915
|
+
"_description": "OTDS user attribute to which the SAML attribute/claim should be mapped",
|
|
2916
|
+
"_value": "mail",
|
|
2917
|
+
},
|
|
2918
|
+
{
|
|
2919
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim2",
|
|
2920
|
+
"_name": "Claim 2",
|
|
2921
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
|
|
2922
|
+
},
|
|
2923
|
+
{
|
|
2924
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute2",
|
|
2925
|
+
"_name": "OTDS Attribute 2",
|
|
2926
|
+
"_value": "givenName",
|
|
2927
|
+
},
|
|
2928
|
+
{
|
|
2929
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim3",
|
|
2930
|
+
"_name": "Claim 3",
|
|
2931
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
|
|
2932
|
+
},
|
|
2933
|
+
{
|
|
2934
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute3",
|
|
2935
|
+
"_name": "OTDS Attribute 3",
|
|
2936
|
+
"_value": "sn",
|
|
2937
|
+
},
|
|
2938
|
+
{
|
|
2939
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim4",
|
|
2940
|
+
"_name": "Claim 4",
|
|
2941
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
|
|
2942
|
+
},
|
|
2943
|
+
{
|
|
2944
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute4",
|
|
2945
|
+
"_name": "OTDS Attribute 4",
|
|
2946
|
+
"_value": "displayName",
|
|
2947
|
+
},
|
|
2948
|
+
{
|
|
2949
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim5",
|
|
2950
|
+
"_name": "Claim 5",
|
|
2951
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/streetaddress",
|
|
2952
|
+
},
|
|
2953
|
+
{
|
|
2954
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute5",
|
|
2955
|
+
"_name": "OTDS Attribute 5",
|
|
2956
|
+
"_value": "oTStreetAddress",
|
|
2957
|
+
},
|
|
2958
|
+
{
|
|
2959
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim6",
|
|
2960
|
+
"_name": "Claim 6",
|
|
2961
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality",
|
|
2962
|
+
},
|
|
2963
|
+
{
|
|
2964
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute6",
|
|
2965
|
+
"_name": "OTDS Attribute 6",
|
|
2966
|
+
"_value": "l",
|
|
2967
|
+
},
|
|
2968
|
+
{
|
|
2969
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim7",
|
|
2970
|
+
"_name": "Claim 7",
|
|
2971
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/stateorprovince",
|
|
2972
|
+
},
|
|
2973
|
+
{
|
|
2974
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute7",
|
|
2975
|
+
"_name": "OTDS Attribute 7",
|
|
2976
|
+
"_value": "st",
|
|
2977
|
+
},
|
|
2978
|
+
{
|
|
2979
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim8",
|
|
2980
|
+
"_name": "Claim 8",
|
|
2981
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode",
|
|
2982
|
+
},
|
|
2983
|
+
{
|
|
2984
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute8",
|
|
2985
|
+
"_name": "OTDS Attribute 8",
|
|
2986
|
+
"_value": "postalCode",
|
|
2987
|
+
},
|
|
2988
|
+
{
|
|
2989
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim9",
|
|
2990
|
+
"_name": "Claim 9",
|
|
2991
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country",
|
|
2992
|
+
},
|
|
2993
|
+
{
|
|
2994
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute9",
|
|
2995
|
+
"_name": "OTDS Attribute 9",
|
|
2996
|
+
"_value": "countryName",
|
|
2997
|
+
},
|
|
2998
|
+
{
|
|
2999
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim10",
|
|
3000
|
+
"_name": "Claim 10",
|
|
3001
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/otherphone",
|
|
3002
|
+
},
|
|
3003
|
+
{
|
|
3004
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute10",
|
|
3005
|
+
"_name": "OTDS Attribute 10",
|
|
3006
|
+
"_value": "oTTelephoneNumber",
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim11",
|
|
3010
|
+
"_name": "Claim 11",
|
|
3011
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/homephone",
|
|
3012
3012
|
},
|
|
3013
3013
|
{
|
|
3014
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3015
|
-
"_name": "
|
|
3016
|
-
"
|
|
3017
|
-
"_value": saml_url,
|
|
3014
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute11",
|
|
3015
|
+
"_name": "OTDS Attribute 11",
|
|
3016
|
+
"_value": "homePhone",
|
|
3018
3017
|
},
|
|
3019
3018
|
{
|
|
3020
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3021
|
-
"_name": "
|
|
3022
|
-
"
|
|
3023
|
-
"_value": nameid_format,
|
|
3019
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim12",
|
|
3020
|
+
"_name": "Claim 12",
|
|
3021
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth",
|
|
3024
3022
|
},
|
|
3025
3023
|
{
|
|
3026
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3027
|
-
"_name": "
|
|
3028
|
-
"
|
|
3029
|
-
"_value": "loggedinuserid",
|
|
3024
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute12",
|
|
3025
|
+
"_name": "OTDS Attribute 12",
|
|
3026
|
+
"_value": "birthDate",
|
|
3030
3027
|
},
|
|
3031
3028
|
{
|
|
3032
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3033
|
-
"_name": "
|
|
3034
|
-
"
|
|
3035
|
-
"_value": otds_sp_endpoint,
|
|
3029
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim13",
|
|
3030
|
+
"_name": "Claim 13",
|
|
3031
|
+
"_value": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender",
|
|
3036
3032
|
},
|
|
3037
3033
|
{
|
|
3038
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3039
|
-
"_name": "
|
|
3040
|
-
"
|
|
3041
|
-
"_value": active_by_default,
|
|
3034
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute13",
|
|
3035
|
+
"_name": "OTDS Attribute 13",
|
|
3036
|
+
"_value": "gender",
|
|
3042
3037
|
},
|
|
3043
3038
|
{
|
|
3044
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3045
|
-
"_name": "
|
|
3046
|
-
"
|
|
3047
|
-
"_value": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
|
3039
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim14",
|
|
3040
|
+
"_name": "Claim 14",
|
|
3041
|
+
"_value": "",
|
|
3048
3042
|
},
|
|
3049
3043
|
{
|
|
3050
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3051
|
-
"_name": "
|
|
3052
|
-
"
|
|
3053
|
-
"_value": "true",
|
|
3044
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute14",
|
|
3045
|
+
"_name": "OTDS Attribute 14",
|
|
3046
|
+
"_value": "",
|
|
3054
3047
|
},
|
|
3055
3048
|
{
|
|
3056
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3057
|
-
"_name": "
|
|
3058
|
-
"
|
|
3059
|
-
"_value": "5",
|
|
3049
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim15",
|
|
3050
|
+
"_name": "Claim 15",
|
|
3051
|
+
"_value": "http://schemas.xmlsoap.org/claims/Group",
|
|
3060
3052
|
},
|
|
3061
3053
|
{
|
|
3062
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3063
|
-
"_name": "
|
|
3064
|
-
"
|
|
3065
|
-
"_value": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
3054
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute15",
|
|
3055
|
+
"_name": "OTDS Attribute 15",
|
|
3056
|
+
"_value": "oTMemberOf",
|
|
3066
3057
|
},
|
|
3067
3058
|
{
|
|
3068
|
-
"_key": "com.opentext.otds.as.drivers.saml.
|
|
3069
|
-
"_name": "
|
|
3070
|
-
"
|
|
3071
|
-
|
|
3059
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim16",
|
|
3060
|
+
"_name": "Claim 16",
|
|
3061
|
+
"_value": "http://schemas.xmlsoap.org/claims/Department",
|
|
3062
|
+
},
|
|
3063
|
+
{
|
|
3064
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute16",
|
|
3065
|
+
"_name": "OTDS Attribute 16",
|
|
3066
|
+
"_value": "oTDepartment",
|
|
3067
|
+
},
|
|
3068
|
+
{
|
|
3069
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim17",
|
|
3070
|
+
"_name": "Claim 17",
|
|
3071
|
+
"_value": "http://schemas.xmlsoap.org/claims/Title",
|
|
3072
|
+
},
|
|
3073
|
+
{
|
|
3074
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute17",
|
|
3075
|
+
"_name": "OTDS Attribute 17",
|
|
3076
|
+
"_value": "title",
|
|
3077
|
+
},
|
|
3078
|
+
{
|
|
3079
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim18",
|
|
3080
|
+
"_name": "Claim 18",
|
|
3081
|
+
"_value": "",
|
|
3082
|
+
},
|
|
3083
|
+
{
|
|
3084
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute18",
|
|
3085
|
+
"_name": "OTDS Attribute 18",
|
|
3086
|
+
"_value": "",
|
|
3087
|
+
},
|
|
3088
|
+
{
|
|
3089
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim19",
|
|
3090
|
+
"_name": "Claim 19",
|
|
3091
|
+
"_value": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
|
|
3092
|
+
},
|
|
3093
|
+
{
|
|
3094
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute19",
|
|
3095
|
+
"_name": "OTDS Attribute 19",
|
|
3096
|
+
"_value": "oTMemberOf",
|
|
3097
|
+
},
|
|
3098
|
+
{
|
|
3099
|
+
"_key": "com.opentext.otds.as.drivers.saml.claim20",
|
|
3100
|
+
"_name": "Claim 20",
|
|
3101
|
+
"_value": "",
|
|
3102
|
+
},
|
|
3103
|
+
{
|
|
3104
|
+
"_key": "com.opentext.otds.as.drivers.saml.claimAttribute20",
|
|
3105
|
+
"_name": "OTDS Attribute 20",
|
|
3106
|
+
"_value": "",
|
|
3072
3107
|
},
|
|
3073
3108
|
],
|
|
3074
3109
|
}
|
|
3075
3110
|
|
|
3076
3111
|
request_url = self.auth_handler_url()
|
|
3077
3112
|
|
|
3078
|
-
logger.
|
|
3079
|
-
"Adding SAML auth handler -> %s (%s); calling -> %s",
|
|
3113
|
+
logger.debug(
|
|
3114
|
+
"Adding SAML auth handler -> '%s' ('%s'); calling -> %s",
|
|
3080
3115
|
name,
|
|
3081
3116
|
description,
|
|
3082
3117
|
request_url,
|
|
3083
3118
|
)
|
|
3084
3119
|
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
timeout=None,
|
|
3093
|
-
)
|
|
3094
|
-
if response.ok:
|
|
3095
|
-
return self.parse_request_response(response)
|
|
3096
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
3097
|
-
elif response.status_code == 401 and retries == 0:
|
|
3098
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
3099
|
-
self.authenticate(revalidate=True)
|
|
3100
|
-
retries += 1
|
|
3101
|
-
else:
|
|
3102
|
-
logger.error(
|
|
3103
|
-
"Failed to add SAML auth handler -> %s; error -> %s (%s)",
|
|
3104
|
-
name,
|
|
3105
|
-
response.text,
|
|
3106
|
-
response.status_code,
|
|
3107
|
-
)
|
|
3108
|
-
return None
|
|
3120
|
+
return self.do_request(
|
|
3121
|
+
url=request_url,
|
|
3122
|
+
method="POST",
|
|
3123
|
+
json_data=auth_handler_post_body_json,
|
|
3124
|
+
timeout=None,
|
|
3125
|
+
failure_message="Failed to add SAML auth handler -> '{}'".format(name),
|
|
3126
|
+
)
|
|
3109
3127
|
|
|
3110
3128
|
# end method definition
|
|
3111
3129
|
|
|
@@ -3140,7 +3158,7 @@ class OTDS:
|
|
|
3140
3158
|
auth_principal_attributes = ["oTExternalID1"]
|
|
3141
3159
|
|
|
3142
3160
|
# 1. Prepare the body for the AuthHandler REST call:
|
|
3143
|
-
|
|
3161
|
+
auth_handler_post_body_json = {
|
|
3144
3162
|
"_name": name,
|
|
3145
3163
|
"_description": description,
|
|
3146
3164
|
"_class": "com.opentext.otds.as.drivers.sapssoext.SAPSSOEXTAuthHandler",
|
|
@@ -3196,43 +3214,40 @@ class OTDS:
|
|
|
3196
3214
|
# 2. Create the auth handler in OTDS
|
|
3197
3215
|
request_url = self.auth_handler_url()
|
|
3198
3216
|
|
|
3199
|
-
logger.
|
|
3200
|
-
"Adding SAP auth handler -> %s (%s); calling -> %s",
|
|
3217
|
+
logger.debug(
|
|
3218
|
+
"Adding SAP auth handler -> '%s' ('%s'); calling -> %s",
|
|
3201
3219
|
name,
|
|
3202
3220
|
description,
|
|
3203
3221
|
request_url,
|
|
3204
3222
|
)
|
|
3205
3223
|
|
|
3206
|
-
response =
|
|
3224
|
+
response = self.do_request(
|
|
3207
3225
|
url=request_url,
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
cookies=self.cookie(),
|
|
3226
|
+
method="POST",
|
|
3227
|
+
json_data=auth_handler_post_body_json,
|
|
3211
3228
|
timeout=None,
|
|
3229
|
+
failure_message="Failed to add SAP auth handler -> '{}'".format(name),
|
|
3230
|
+
parse_request_response=False,
|
|
3212
3231
|
)
|
|
3213
|
-
if not response.ok:
|
|
3214
|
-
logger.error(
|
|
3215
|
-
"Failed to add SAP auth handler -> %s; error -> %s (%s)",
|
|
3216
|
-
name,
|
|
3217
|
-
response.text,
|
|
3218
|
-
response.status_code,
|
|
3219
|
-
)
|
|
3232
|
+
if not response or not response.ok:
|
|
3220
3233
|
return None
|
|
3221
3234
|
|
|
3222
3235
|
# 3. Upload the certificate file:
|
|
3223
3236
|
|
|
3224
3237
|
# Check that the certificate (PSE) file is readable:
|
|
3225
|
-
logger.
|
|
3238
|
+
logger.debug("Reading certificate file -> '%s'...", certificate_file)
|
|
3226
3239
|
try:
|
|
3227
3240
|
# PSE files are binary - so we need to open with "rb":
|
|
3228
|
-
with open(certificate_file, "rb") as
|
|
3229
|
-
|
|
3230
|
-
if not
|
|
3231
|
-
logger.error(
|
|
3241
|
+
with open(certificate_file, "rb") as cert_file:
|
|
3242
|
+
cert_content = cert_file.read()
|
|
3243
|
+
if not cert_content:
|
|
3244
|
+
logger.error(
|
|
3245
|
+
"No data in certificate file -> '%s'", certificate_file
|
|
3246
|
+
)
|
|
3232
3247
|
return None
|
|
3233
3248
|
except IOError as exception:
|
|
3234
3249
|
logger.error(
|
|
3235
|
-
"Unable to open certificate file -> %s; error -> %s",
|
|
3250
|
+
"Unable to open certificate file -> '%s'; error -> %s",
|
|
3236
3251
|
certificate_file,
|
|
3237
3252
|
exception.strerror,
|
|
3238
3253
|
)
|
|
@@ -3243,43 +3258,45 @@ class OTDS:
|
|
|
3243
3258
|
try:
|
|
3244
3259
|
# If file is not base64 encoded the next statement will throw an exception
|
|
3245
3260
|
# (this is good)
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3261
|
+
cert_content_decoded = base64.b64decode(cert_content, validate=True)
|
|
3262
|
+
cert_content_encoded = base64.b64encode(cert_content_decoded).decode(
|
|
3263
|
+
"utf-8"
|
|
3264
|
+
)
|
|
3265
|
+
if cert_content_encoded == cert_content.decode("utf-8"):
|
|
3266
|
+
logger.debug(
|
|
3267
|
+
"Certificate file -> '%s' is base64 encoded", certificate_file
|
|
3251
3268
|
)
|
|
3252
3269
|
cert_file_encoded = True
|
|
3253
3270
|
else:
|
|
3254
3271
|
cert_file_encoded = False
|
|
3255
3272
|
except TypeError:
|
|
3256
|
-
logger.
|
|
3257
|
-
"Certificate file -> %s is not base64 encoded", certificate_file
|
|
3273
|
+
logger.debug(
|
|
3274
|
+
"Certificate file -> '%s' is not base64 encoded", certificate_file
|
|
3258
3275
|
)
|
|
3259
3276
|
cert_file_encoded = False
|
|
3260
3277
|
|
|
3261
3278
|
if cert_file_encoded:
|
|
3262
3279
|
certificate_file = "/tmp/" + os.path.basename(certificate_file)
|
|
3263
|
-
logger.
|
|
3280
|
+
logger.debug("Writing decoded certificate file -> %s...", certificate_file)
|
|
3264
3281
|
try:
|
|
3265
3282
|
# PSE files need to be binary - so we need to open with "wb":
|
|
3266
|
-
with open(certificate_file, "wb") as
|
|
3267
|
-
|
|
3283
|
+
with open(certificate_file, "wb") as cert_file:
|
|
3284
|
+
cert_file.write(base64.b64decode(cert_content))
|
|
3268
3285
|
except IOError as exception:
|
|
3269
3286
|
logger.error(
|
|
3270
|
-
"Failed writing to file -> %s; error -> %s",
|
|
3287
|
+
"Failed writing to file -> '%s'; error -> %s",
|
|
3271
3288
|
certificate_file,
|
|
3272
3289
|
exception.strerror,
|
|
3273
3290
|
)
|
|
3274
3291
|
return None
|
|
3275
3292
|
|
|
3276
|
-
|
|
3293
|
+
auth_handler_post_data = {
|
|
3277
3294
|
"file1_property": "com.opentext.otds.as.drivers.sapssoext.certificate1"
|
|
3278
3295
|
}
|
|
3279
3296
|
|
|
3280
3297
|
# It is important to send the file pointer and not the actual file content
|
|
3281
3298
|
# otherwise the file is send base64 encoded which we don't want:
|
|
3282
|
-
|
|
3299
|
+
auth_handler_post_files = {
|
|
3283
3300
|
"file1": (
|
|
3284
3301
|
os.path.basename(certificate_file),
|
|
3285
3302
|
open(certificate_file, "rb"),
|
|
@@ -3289,8 +3306,8 @@ class OTDS:
|
|
|
3289
3306
|
|
|
3290
3307
|
request_url = self.auth_handler_url() + "/" + name + "/files"
|
|
3291
3308
|
|
|
3292
|
-
logger.
|
|
3293
|
-
"Uploading certificate file -> %s for SAP auth handler -> %s (%s); calling -> %s",
|
|
3309
|
+
logger.debug(
|
|
3310
|
+
"Uploading certificate file -> '%s' for SAP auth handler -> '%s' ('%s'); calling -> %s",
|
|
3294
3311
|
certificate_file,
|
|
3295
3312
|
name,
|
|
3296
3313
|
description,
|
|
@@ -3302,14 +3319,14 @@ class OTDS:
|
|
|
3302
3319
|
# then requests will send a multipart/form-data POST automatically:
|
|
3303
3320
|
response = requests.post(
|
|
3304
3321
|
url=request_url,
|
|
3305
|
-
data=
|
|
3306
|
-
files=
|
|
3322
|
+
data=auth_handler_post_data,
|
|
3323
|
+
files=auth_handler_post_files,
|
|
3307
3324
|
cookies=self.cookie(),
|
|
3308
3325
|
timeout=None,
|
|
3309
3326
|
)
|
|
3310
3327
|
if not response.ok:
|
|
3311
3328
|
logger.error(
|
|
3312
|
-
"Failed to upload certificate file -> %s for SAP auth handler -> %s; error -> %s (%s)",
|
|
3329
|
+
"Failed to upload certificate file -> '%s' for SAP auth handler -> '%s'; error -> %s (%s)",
|
|
3313
3330
|
certificate_file,
|
|
3314
3331
|
name,
|
|
3315
3332
|
response.text,
|
|
@@ -3366,7 +3383,7 @@ class OTDS:
|
|
|
3366
3383
|
auth_principal_attributes = ["oTExtraAttr0"]
|
|
3367
3384
|
|
|
3368
3385
|
# 1. Prepare the body for the AuthHandler REST call:
|
|
3369
|
-
|
|
3386
|
+
auth_handler_post_body_json = {
|
|
3370
3387
|
"_name": name,
|
|
3371
3388
|
"_description": description,
|
|
3372
3389
|
"_class": "com.opentext.otds.as.drivers.http.OAuth2Handler",
|
|
@@ -3705,37 +3722,20 @@ class OTDS:
|
|
|
3705
3722
|
|
|
3706
3723
|
request_url = self.auth_handler_url()
|
|
3707
3724
|
|
|
3708
|
-
logger.
|
|
3709
|
-
"Adding OAuth auth handler -> %s (%s); calling -> %s",
|
|
3725
|
+
logger.debug(
|
|
3726
|
+
"Adding OAuth auth handler -> '%s' ('%s'); calling -> %s",
|
|
3710
3727
|
name,
|
|
3711
3728
|
description,
|
|
3712
3729
|
request_url,
|
|
3713
3730
|
)
|
|
3714
3731
|
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
timeout=None,
|
|
3723
|
-
)
|
|
3724
|
-
if response.ok:
|
|
3725
|
-
return self.parse_request_response(response)
|
|
3726
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
3727
|
-
elif response.status_code == 401 and retries == 0:
|
|
3728
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
3729
|
-
self.authenticate(revalidate=True)
|
|
3730
|
-
retries += 1
|
|
3731
|
-
else:
|
|
3732
|
-
logger.error(
|
|
3733
|
-
"Failed to add OAuth auth handler -> %s; error -> %s (%s)",
|
|
3734
|
-
name,
|
|
3735
|
-
response.text,
|
|
3736
|
-
response.status_code,
|
|
3737
|
-
)
|
|
3738
|
-
return None
|
|
3732
|
+
return self.do_request(
|
|
3733
|
+
url=request_url,
|
|
3734
|
+
method="POST",
|
|
3735
|
+
json_data=auth_handler_post_body_json,
|
|
3736
|
+
timeout=None,
|
|
3737
|
+
failure_message="Failed to add OAuth auth handler -> '{}'".format(name),
|
|
3738
|
+
)
|
|
3739
3739
|
|
|
3740
3740
|
# end method definition
|
|
3741
3741
|
|
|
@@ -3750,7 +3750,9 @@ class OTDS:
|
|
|
3750
3750
|
|
|
3751
3751
|
resource = self.get_resource(resource_name)
|
|
3752
3752
|
if not resource:
|
|
3753
|
-
logger.error(
|
|
3753
|
+
logger.error(
|
|
3754
|
+
"Resource -> '%s' not found - cannot consolidate", resource_name
|
|
3755
|
+
)
|
|
3754
3756
|
return False
|
|
3755
3757
|
|
|
3756
3758
|
resource_dn = resource["resourceDN"]
|
|
@@ -3758,7 +3760,7 @@ class OTDS:
|
|
|
3758
3760
|
logger.error("Resource DN is empty - cannot consolidate")
|
|
3759
3761
|
return False
|
|
3760
3762
|
|
|
3761
|
-
|
|
3763
|
+
consolidation_post_body_json = {
|
|
3762
3764
|
"cleanupUsersInResource": False,
|
|
3763
3765
|
"cleanupGroupsInResource": False,
|
|
3764
3766
|
"resourceList": [resource_dn],
|
|
@@ -3767,33 +3769,28 @@ class OTDS:
|
|
|
3767
3769
|
|
|
3768
3770
|
request_url = "{}".format(self.consolidation_url())
|
|
3769
3771
|
|
|
3770
|
-
logger.
|
|
3771
|
-
"Consolidation of resource -> %s; calling -> %s",
|
|
3772
|
+
logger.debug(
|
|
3773
|
+
"Consolidation of resource -> %s (%s); calling -> %s",
|
|
3774
|
+
resource_name,
|
|
3775
|
+
resource_dn,
|
|
3776
|
+
request_url,
|
|
3772
3777
|
)
|
|
3773
3778
|
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
retries += 1
|
|
3790
|
-
else:
|
|
3791
|
-
logger.error(
|
|
3792
|
-
"Failed to consolidate; error -> %s (%s)",
|
|
3793
|
-
response.text,
|
|
3794
|
-
response.status_code,
|
|
3795
|
-
)
|
|
3796
|
-
return False
|
|
3779
|
+
response = self.do_request(
|
|
3780
|
+
url=request_url,
|
|
3781
|
+
method="POST",
|
|
3782
|
+
json_data=consolidation_post_body_json,
|
|
3783
|
+
timeout=None,
|
|
3784
|
+
failure_message="Failed to consolidate resource -> '{}'".format(
|
|
3785
|
+
resource_name
|
|
3786
|
+
),
|
|
3787
|
+
parse_request_response=False,
|
|
3788
|
+
)
|
|
3789
|
+
|
|
3790
|
+
if response and response.ok:
|
|
3791
|
+
return True
|
|
3792
|
+
|
|
3793
|
+
return False
|
|
3797
3794
|
|
|
3798
3795
|
# end method definition
|
|
3799
3796
|
|
|
@@ -3818,42 +3815,34 @@ class OTDS:
|
|
|
3818
3815
|
if impersonation_list is None:
|
|
3819
3816
|
impersonation_list = []
|
|
3820
3817
|
|
|
3821
|
-
|
|
3818
|
+
impersonation_put_body_json = {
|
|
3822
3819
|
"allowImpersonation": allow_impersonation,
|
|
3823
3820
|
"impersonateList": impersonation_list,
|
|
3824
3821
|
}
|
|
3825
3822
|
|
|
3826
3823
|
request_url = "{}/{}/impersonation".format(self.resource_url(), resource_name)
|
|
3827
3824
|
|
|
3828
|
-
logger.
|
|
3829
|
-
"Impersonation settings for resource -> %s; calling -> %s",
|
|
3825
|
+
logger.debug(
|
|
3826
|
+
"Impersonation settings for resource -> '%s'; calling -> %s",
|
|
3830
3827
|
resource_name,
|
|
3831
3828
|
request_url,
|
|
3832
3829
|
)
|
|
3833
3830
|
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
retries += 1
|
|
3850
|
-
else:
|
|
3851
|
-
logger.error(
|
|
3852
|
-
"Failed to set impersonation for resource -> %s; error -> %s",
|
|
3853
|
-
resource_name,
|
|
3854
|
-
response.text,
|
|
3855
|
-
)
|
|
3856
|
-
return False
|
|
3831
|
+
response = self.do_request(
|
|
3832
|
+
url=request_url,
|
|
3833
|
+
method="PUT",
|
|
3834
|
+
json_data=impersonation_put_body_json,
|
|
3835
|
+
timeout=None,
|
|
3836
|
+
failure_message="Failed to set impersonation for resource -> '{}'".format(
|
|
3837
|
+
resource_name
|
|
3838
|
+
),
|
|
3839
|
+
parse_request_response=False,
|
|
3840
|
+
)
|
|
3841
|
+
|
|
3842
|
+
if response and response.ok:
|
|
3843
|
+
return True
|
|
3844
|
+
|
|
3845
|
+
return False
|
|
3857
3846
|
|
|
3858
3847
|
# end method definition
|
|
3859
3848
|
|
|
@@ -3877,43 +3866,34 @@ class OTDS:
|
|
|
3877
3866
|
if impersonation_list is None:
|
|
3878
3867
|
impersonation_list = []
|
|
3879
3868
|
|
|
3880
|
-
|
|
3869
|
+
impersonation_put_body_json = {
|
|
3881
3870
|
"allowImpersonation": allow_impersonation,
|
|
3882
3871
|
"impersonateList": impersonation_list,
|
|
3883
3872
|
}
|
|
3884
3873
|
|
|
3885
3874
|
request_url = "{}/{}/impersonation".format(self.oauth_client_url(), client_id)
|
|
3886
3875
|
|
|
3887
|
-
logger.
|
|
3888
|
-
"Impersonation settings for OAuth Client -> %s; calling -> %s",
|
|
3876
|
+
logger.debug(
|
|
3877
|
+
"Impersonation settings for OAuth Client -> '%s'; calling -> %s",
|
|
3889
3878
|
client_id,
|
|
3890
3879
|
request_url,
|
|
3891
3880
|
)
|
|
3892
3881
|
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
retries += 1
|
|
3909
|
-
else:
|
|
3910
|
-
logger.error(
|
|
3911
|
-
"Failed to set impersonation for OAuth Client -> %s; error -> %s (%s)",
|
|
3912
|
-
client_id,
|
|
3913
|
-
response.text,
|
|
3914
|
-
response.status_code,
|
|
3915
|
-
)
|
|
3916
|
-
return False
|
|
3882
|
+
response = self.do_request(
|
|
3883
|
+
url=request_url,
|
|
3884
|
+
method="PUT",
|
|
3885
|
+
json_data=impersonation_put_body_json,
|
|
3886
|
+
timeout=None,
|
|
3887
|
+
failure_message="Failed to set impersonation for OAuth Client -> '{}'".format(
|
|
3888
|
+
client_id
|
|
3889
|
+
),
|
|
3890
|
+
parse_request_response=False,
|
|
3891
|
+
)
|
|
3892
|
+
|
|
3893
|
+
if response and response.ok:
|
|
3894
|
+
return True
|
|
3895
|
+
|
|
3896
|
+
return False
|
|
3917
3897
|
|
|
3918
3898
|
# end method definition
|
|
3919
3899
|
|
|
@@ -3948,30 +3928,14 @@ class OTDS:
|
|
|
3948
3928
|
|
|
3949
3929
|
request_url = "{}/passwordpolicy".format(self.config()["systemConfigUrl"])
|
|
3950
3930
|
|
|
3951
|
-
logger.
|
|
3931
|
+
logger.debug("Getting password policy; calling -> %s", request_url)
|
|
3952
3932
|
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
timeout=None,
|
|
3960
|
-
)
|
|
3961
|
-
if response.ok:
|
|
3962
|
-
return self.parse_request_response(response)
|
|
3963
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
3964
|
-
elif response.status_code == 401 and retries == 0:
|
|
3965
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
3966
|
-
self.authenticate(revalidate=True)
|
|
3967
|
-
retries += 1
|
|
3968
|
-
else:
|
|
3969
|
-
logger.error(
|
|
3970
|
-
"Failed to get password policy; error -> %s (%s)",
|
|
3971
|
-
response.text,
|
|
3972
|
-
response.status_code,
|
|
3973
|
-
)
|
|
3974
|
-
return None
|
|
3933
|
+
return self.do_request(
|
|
3934
|
+
url=request_url,
|
|
3935
|
+
method="GET",
|
|
3936
|
+
timeout=None,
|
|
3937
|
+
failure_message="Failed to get password policy",
|
|
3938
|
+
)
|
|
3975
3939
|
|
|
3976
3940
|
# end method definition
|
|
3977
3941
|
|
|
@@ -4009,35 +3973,26 @@ class OTDS:
|
|
|
4009
3973
|
|
|
4010
3974
|
request_url = "{}/passwordpolicy".format(self.config()["systemConfigUrl"])
|
|
4011
3975
|
|
|
4012
|
-
logger.
|
|
3976
|
+
logger.debug(
|
|
4013
3977
|
"Update password policy with these new values -> %s; calling -> %s",
|
|
4014
|
-
update_values,
|
|
3978
|
+
str(update_values),
|
|
4015
3979
|
request_url,
|
|
4016
3980
|
)
|
|
4017
3981
|
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
retries += 1
|
|
4034
|
-
else:
|
|
4035
|
-
logger.error(
|
|
4036
|
-
"Failed to update password policy with values -> %s; error -> %s (%s)",
|
|
4037
|
-
update_values,
|
|
4038
|
-
response.text,
|
|
4039
|
-
response.status_code,
|
|
4040
|
-
)
|
|
4041
|
-
return False
|
|
3982
|
+
response = self.do_request(
|
|
3983
|
+
url=request_url,
|
|
3984
|
+
method="PUT",
|
|
3985
|
+
json_data=update_values,
|
|
3986
|
+
timeout=None,
|
|
3987
|
+
failure_message="Failed to update password policy with values -> {}".format(
|
|
3988
|
+
update_values
|
|
3989
|
+
),
|
|
3990
|
+
parse_request_response=False,
|
|
3991
|
+
)
|
|
3992
|
+
|
|
3993
|
+
if response and response.ok:
|
|
3994
|
+
return True
|
|
3995
|
+
|
|
3996
|
+
return False
|
|
4042
3997
|
|
|
4043
3998
|
# end method definition
|