pyxecm 1.5__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 +2 -0
- pyxecm/avts.py +1065 -0
- pyxecm/coreshare.py +467 -571
- pyxecm/customizer/customizer.py +160 -19
- pyxecm/customizer/k8s.py +139 -25
- pyxecm/customizer/m365.py +694 -1498
- pyxecm/customizer/payload.py +2306 -485
- pyxecm/customizer/pht.py +547 -124
- pyxecm/customizer/salesforce.py +378 -443
- pyxecm/customizer/servicenow.py +379 -133
- pyxecm/helper/assoc.py +20 -0
- pyxecm/helper/data.py +237 -33
- pyxecm/helper/xml.py +1 -1
- pyxecm/otawp.py +1810 -0
- pyxecm/otcs.py +3180 -2938
- pyxecm/otds.py +1591 -1875
- pyxecm/otmm.py +131 -11
- {pyxecm-1.5.dist-info → pyxecm-1.6.dist-info}/METADATA +3 -1
- pyxecm-1.6.dist-info/RECORD +32 -0
- {pyxecm-1.5.dist-info → pyxecm-1.6.dist-info}/WHEEL +1 -1
- pyxecm-1.5.dist-info/RECORD +0 -30
- {pyxecm-1.5.dist-info → pyxecm-1.6.dist-info}/LICENSE +0 -0
- {pyxecm-1.5.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"
|
|
@@ -278,6 +296,14 @@ class OTDS:
|
|
|
278
296
|
return self.config()["authHandlerUrl"]
|
|
279
297
|
|
|
280
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
|
|
281
307
|
|
|
282
308
|
def partition_url(self) -> str:
|
|
283
309
|
"""Returns the Partition URL of OTDS
|
|
@@ -379,6 +405,164 @@ class OTDS:
|
|
|
379
405
|
|
|
380
406
|
# end method definition
|
|
381
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
|
+
|
|
382
566
|
def parse_request_response(
|
|
383
567
|
self,
|
|
384
568
|
response_object: object,
|
|
@@ -399,6 +583,10 @@ class OTDS:
|
|
|
399
583
|
if not response_object:
|
|
400
584
|
return None
|
|
401
585
|
|
|
586
|
+
if not response_object.text:
|
|
587
|
+
logger.warning("Response text is empty. Cannot decode response.")
|
|
588
|
+
return None
|
|
589
|
+
|
|
402
590
|
try:
|
|
403
591
|
dict_object = json.loads(response_object.text)
|
|
404
592
|
except json.JSONDecodeError as e:
|
|
@@ -476,1816 +664,1563 @@ class OTDS:
|
|
|
476
664
|
|
|
477
665
|
# end method definition
|
|
478
666
|
|
|
479
|
-
def
|
|
480
|
-
|
|
481
|
-
path_to_license_file: str,
|
|
482
|
-
product_name: str,
|
|
483
|
-
product_description: str,
|
|
484
|
-
resource_id: str,
|
|
485
|
-
update: bool = True,
|
|
486
|
-
) -> dict | None:
|
|
487
|
-
"""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
|
|
488
669
|
|
|
489
670
|
Args:
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
product_description (str): product description
|
|
493
|
-
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
494
|
-
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
|
|
495
673
|
Returns:
|
|
496
|
-
dict: Request response
|
|
674
|
+
dict: Request response or None if the creation fails.
|
|
497
675
|
"""
|
|
498
676
|
|
|
499
|
-
|
|
500
|
-
try:
|
|
501
|
-
with open(path_to_license_file, "rt", encoding="UTF-8") as license_file:
|
|
502
|
-
license_content = license_file.read()
|
|
503
|
-
except IOError as exception:
|
|
504
|
-
logger.error(
|
|
505
|
-
"Error opening license file -> %s; error -> %s",
|
|
506
|
-
path_to_license_file,
|
|
507
|
-
exception.strerror,
|
|
508
|
-
)
|
|
509
|
-
return None
|
|
510
|
-
|
|
511
|
-
licensePostBodyJson = {
|
|
512
|
-
"description": product_description,
|
|
513
|
-
"name": product_name,
|
|
514
|
-
"values": [
|
|
515
|
-
{"name": "oTLicenseFile", "values": license_content},
|
|
516
|
-
{"name": "oTLicenseResource", "values": resource_id},
|
|
517
|
-
{"name": "oTLicenseFingerprintGenerator", "values": [None]},
|
|
518
|
-
],
|
|
519
|
-
}
|
|
677
|
+
partition_post_body_json = {"name": name, "description": description}
|
|
520
678
|
|
|
521
|
-
request_url = self.
|
|
522
|
-
# Check if we want to update an existing license:
|
|
523
|
-
if update:
|
|
524
|
-
existing_license = self.get_license_for_resource(resource_id)
|
|
525
|
-
if existing_license:
|
|
526
|
-
request_url += "/" + existing_license[0]["id"]
|
|
527
|
-
else:
|
|
528
|
-
logger.debug(
|
|
529
|
-
"No existing license for resource -> %s found - adding a new license...",
|
|
530
|
-
resource_id,
|
|
531
|
-
)
|
|
532
|
-
# change strategy to create a new license:
|
|
533
|
-
update = False
|
|
679
|
+
request_url = self.partition_url()
|
|
534
680
|
|
|
535
681
|
logger.debug(
|
|
536
|
-
"Adding
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
resource_id,
|
|
682
|
+
"Adding user partition -> '%s' (%s); calling -> %s",
|
|
683
|
+
name,
|
|
684
|
+
description,
|
|
540
685
|
request_url,
|
|
541
686
|
)
|
|
542
687
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
headers=REQUEST_HEADERS,
|
|
551
|
-
cookies=self.cookie(),
|
|
552
|
-
timeout=None,
|
|
553
|
-
)
|
|
554
|
-
else:
|
|
555
|
-
# Do a REST POST call for creation of a new license:
|
|
556
|
-
response = requests.post(
|
|
557
|
-
url=request_url,
|
|
558
|
-
json=licensePostBodyJson,
|
|
559
|
-
headers=REQUEST_HEADERS,
|
|
560
|
-
cookies=self.cookie(),
|
|
561
|
-
timeout=None,
|
|
562
|
-
)
|
|
563
|
-
if response.ok:
|
|
564
|
-
return self.parse_request_response(response)
|
|
565
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
566
|
-
elif response.status_code == 401 and retries == 0:
|
|
567
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
568
|
-
self.authenticate(revalidate=True)
|
|
569
|
-
retries += 1
|
|
570
|
-
else:
|
|
571
|
-
logger.error(
|
|
572
|
-
"Failed to add product license -> %s for product -> %s; error -> %s (%s)",
|
|
573
|
-
path_to_license_file,
|
|
574
|
-
product_description,
|
|
575
|
-
response.text,
|
|
576
|
-
response.status_code,
|
|
577
|
-
)
|
|
578
|
-
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
|
+
)
|
|
579
695
|
|
|
580
696
|
# end method definition
|
|
581
697
|
|
|
582
|
-
def
|
|
583
|
-
"""Get
|
|
698
|
+
def get_partition(self, name: str, show_error: bool = True) -> dict | None:
|
|
699
|
+
"""Get an existing user partition from OTDS
|
|
584
700
|
|
|
585
701
|
Args:
|
|
586
|
-
|
|
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
|
|
587
705
|
Returns:
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
licenses have this format:
|
|
591
|
-
{
|
|
592
|
-
'_oTLicenseType': 'NON-PRODUCTION',
|
|
593
|
-
'_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
594
|
-
'_oTLicenseResourceName': 'cs',
|
|
595
|
-
'_oTLicenseProduct': 'EXTENDED_ECM',
|
|
596
|
-
'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
597
|
-
'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
598
|
-
'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
599
|
-
'description': 'CS license',
|
|
600
|
-
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
601
|
-
}
|
|
706
|
+
dict: Request response or None if the REST call fails.
|
|
602
707
|
"""
|
|
603
708
|
|
|
604
|
-
request_url = (
|
|
605
|
-
self.license_url()
|
|
606
|
-
+ "/assignedlicenses?resourceID="
|
|
607
|
-
+ resource_id
|
|
608
|
-
+ "&validOnly=false"
|
|
609
|
-
)
|
|
709
|
+
request_url = "{}/{}".format(self.config()["partitionUrl"], name)
|
|
610
710
|
|
|
611
|
-
logger.debug(
|
|
612
|
-
"Get license for resource -> %s; calling -> %s", resource_id, request_url
|
|
613
|
-
)
|
|
711
|
+
logger.debug("Get user partition -> '%s'; calling -> %s", name, request_url)
|
|
614
712
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
)
|
|
623
|
-
if response.ok:
|
|
624
|
-
response_dict = self.parse_request_response(response)
|
|
625
|
-
if not response_dict:
|
|
626
|
-
return None
|
|
627
|
-
return response_dict["licenseObjects"]["_licenses"]
|
|
628
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
629
|
-
elif response.status_code == 401 and retries == 0:
|
|
630
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
631
|
-
self.authenticate(revalidate=True)
|
|
632
|
-
retries += 1
|
|
633
|
-
else:
|
|
634
|
-
logger.error(
|
|
635
|
-
"Failed to get license for resource -> %s; error -> %s (%s)",
|
|
636
|
-
resource_id,
|
|
637
|
-
response.text,
|
|
638
|
-
response.status_code,
|
|
639
|
-
)
|
|
640
|
-
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
|
+
)
|
|
641
720
|
|
|
642
721
|
# end method definition
|
|
643
722
|
|
|
644
|
-
def
|
|
645
|
-
|
|
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
|
|
646
733
|
|
|
647
734
|
Args:
|
|
648
|
-
|
|
649
|
-
|
|
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
|
|
650
741
|
Returns:
|
|
651
|
-
|
|
742
|
+
dict: Request response or None if the creation fails.
|
|
652
743
|
"""
|
|
653
744
|
|
|
654
|
-
|
|
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()
|
|
655
757
|
|
|
656
758
|
logger.debug(
|
|
657
|
-
"
|
|
658
|
-
|
|
659
|
-
|
|
759
|
+
"Adding user -> '%s' to partition -> '%s'; calling -> %s",
|
|
760
|
+
name,
|
|
761
|
+
partition,
|
|
660
762
|
request_url,
|
|
661
763
|
)
|
|
764
|
+
logger.debug("User Attributes -> %s", str(user_post_body_json))
|
|
662
765
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
)
|
|
671
|
-
if response.ok:
|
|
672
|
-
return True
|
|
673
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
674
|
-
elif response.status_code == 401 and retries == 0:
|
|
675
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
676
|
-
self.authenticate(revalidate=True)
|
|
677
|
-
retries += 1
|
|
678
|
-
else:
|
|
679
|
-
logger.error(
|
|
680
|
-
"Failed to delete license -> %s for resource -> %s; error -> %s (%s)",
|
|
681
|
-
license_id,
|
|
682
|
-
resource_id,
|
|
683
|
-
response.text,
|
|
684
|
-
response.status_code,
|
|
685
|
-
)
|
|
686
|
-
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
|
+
)
|
|
687
773
|
|
|
688
774
|
# end method definition
|
|
689
775
|
|
|
690
|
-
def
|
|
691
|
-
|
|
692
|
-
partition: str,
|
|
693
|
-
user_id: str,
|
|
694
|
-
resource_id: str,
|
|
695
|
-
license_feature: str,
|
|
696
|
-
license_name: str,
|
|
697
|
-
license_type: str = "Full",
|
|
698
|
-
) -> bool:
|
|
699
|
-
"""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
|
|
700
778
|
|
|
701
779
|
Args:
|
|
702
|
-
partition (str):
|
|
780
|
+
partition (str): name of the partition
|
|
703
781
|
user_id (str): ID of the user (= login name)
|
|
704
|
-
resource_id (str): OTDS resource ID (this is ID not the resource name!)
|
|
705
|
-
license_feature (str): name of the license feature
|
|
706
|
-
license_name (str): name of the license to assign
|
|
707
|
-
license_type (str, optional): deault is "Full", Extended ECM also has "Occasional"
|
|
708
782
|
Returns:
|
|
709
|
-
|
|
783
|
+
dict: Request response or None if the user was not found.
|
|
710
784
|
"""
|
|
711
785
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
for lic in licenses:
|
|
715
|
-
if lic["_oTLicenseProduct"] == license_name:
|
|
716
|
-
license_location = lic["id"]
|
|
717
|
-
|
|
718
|
-
try:
|
|
719
|
-
license_location
|
|
720
|
-
except UnboundLocalError:
|
|
721
|
-
logger.error(
|
|
722
|
-
"Cannot find license -> %s for resource -> %s",
|
|
723
|
-
license_name,
|
|
724
|
-
resource_id,
|
|
725
|
-
)
|
|
726
|
-
return False
|
|
727
|
-
|
|
728
|
-
user = self.get_user(partition, user_id)
|
|
729
|
-
if user:
|
|
730
|
-
user_location = user["location"]
|
|
731
|
-
else:
|
|
732
|
-
logger.error("Cannot find location for user -> %s", user_id)
|
|
733
|
-
return False
|
|
734
|
-
|
|
735
|
-
licensePostBodyJson = {
|
|
736
|
-
"_oTLicenseType": license_type,
|
|
737
|
-
"_oTLicenseProduct": "users",
|
|
738
|
-
"name": user_location,
|
|
739
|
-
"values": [{"name": "counter", "values": [license_feature]}],
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
request_url = self.license_url() + "/object/" + license_location
|
|
786
|
+
request_url = self.users_url() + "/" + user_id + "@" + partition
|
|
743
787
|
|
|
744
788
|
logger.debug(
|
|
745
|
-
"
|
|
746
|
-
license_feature,
|
|
747
|
-
license_location,
|
|
748
|
-
resource_id,
|
|
789
|
+
"Get user -> '%s' in partition -> '%s'; calling -> %s",
|
|
749
790
|
user_id,
|
|
791
|
+
partition,
|
|
750
792
|
request_url,
|
|
751
793
|
)
|
|
752
794
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
cookies=self.cookie(),
|
|
760
|
-
timeout=None,
|
|
761
|
-
)
|
|
762
|
-
if response.ok:
|
|
763
|
-
logger.debug(
|
|
764
|
-
"Added license feature -> %s for user -> %s",
|
|
765
|
-
license_feature,
|
|
766
|
-
user_id,
|
|
767
|
-
)
|
|
768
|
-
return True
|
|
769
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
770
|
-
elif response.status_code == 401 and retries == 0:
|
|
771
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
772
|
-
self.authenticate(revalidate=True)
|
|
773
|
-
retries += 1
|
|
774
|
-
else:
|
|
775
|
-
logger.error(
|
|
776
|
-
"Failed to add license feature -> %s associated with resource -> %s to user -> %s; error -> %s (%s)",
|
|
777
|
-
license_feature,
|
|
778
|
-
resource_id,
|
|
779
|
-
user_id,
|
|
780
|
-
response.text,
|
|
781
|
-
response.status_code,
|
|
782
|
-
)
|
|
783
|
-
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
|
+
)
|
|
784
801
|
|
|
785
802
|
# end method definition
|
|
786
803
|
|
|
787
|
-
def
|
|
788
|
-
|
|
789
|
-
partition_name: str,
|
|
790
|
-
resource_id: str,
|
|
791
|
-
license_feature: str,
|
|
792
|
-
license_name: str,
|
|
793
|
-
license_type: str = "Full",
|
|
794
|
-
) -> bool:
|
|
795
|
-
"""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
|
|
796
806
|
|
|
797
807
|
Args:
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
801
|
-
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
802
|
-
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
|
|
803
810
|
Returns:
|
|
804
|
-
|
|
811
|
+
dict: Request response or None if the user was not found.
|
|
805
812
|
"""
|
|
806
813
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
# licenses have this format:
|
|
815
|
-
# {
|
|
816
|
-
# '_oTLicenseType': 'NON-PRODUCTION',
|
|
817
|
-
# '_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
818
|
-
# '_oTLicenseResourceName': 'cs',
|
|
819
|
-
# '_oTLicenseProduct': 'EXTENDED_ECM',
|
|
820
|
-
# 'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
821
|
-
# 'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
822
|
-
# 'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
823
|
-
# 'description': 'CS license',
|
|
824
|
-
# 'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
825
|
-
# }
|
|
826
|
-
for lic in licenses:
|
|
827
|
-
if lic["_oTLicenseProduct"] == license_name:
|
|
828
|
-
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
|
|
829
820
|
|
|
830
|
-
|
|
831
|
-
license_location
|
|
832
|
-
except UnboundLocalError:
|
|
833
|
-
logger.error(
|
|
834
|
-
"Cannot find license -> %s for resource -> %s",
|
|
835
|
-
license_name,
|
|
836
|
-
resource_id,
|
|
837
|
-
)
|
|
838
|
-
return False
|
|
821
|
+
encoded_query = urllib.parse.urlencode(query, doseq=True)
|
|
839
822
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
"name": partition_name,
|
|
844
|
-
"values": [{"name": "counter", "values": [license_feature]}],
|
|
845
|
-
}
|
|
823
|
+
request_url = self.users_url()
|
|
824
|
+
if query:
|
|
825
|
+
request_url += "?{}".format(encoded_query)
|
|
846
826
|
|
|
847
|
-
|
|
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"
|
|
848
844
|
|
|
849
|
-
|
|
850
|
-
"
|
|
851
|
-
license_feature,
|
|
852
|
-
license_location,
|
|
853
|
-
resource_id,
|
|
854
|
-
partition_name,
|
|
855
|
-
request_url,
|
|
845
|
+
return self.do_request(
|
|
846
|
+
url=request_url, method="GET", timeout=None, failure_message=failure_message
|
|
856
847
|
)
|
|
857
848
|
|
|
858
|
-
retries = 0
|
|
859
|
-
while True:
|
|
860
|
-
response = requests.post(
|
|
861
|
-
url=request_url,
|
|
862
|
-
json=licensePostBodyJson,
|
|
863
|
-
headers=REQUEST_HEADERS,
|
|
864
|
-
cookies=self.cookie(),
|
|
865
|
-
timeout=None,
|
|
866
|
-
)
|
|
867
|
-
if response.ok:
|
|
868
|
-
logger.debug(
|
|
869
|
-
"Added license feature -> %s for partition -> %s",
|
|
870
|
-
license_feature,
|
|
871
|
-
partition_name,
|
|
872
|
-
)
|
|
873
|
-
return True
|
|
874
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
875
|
-
elif response.status_code == 401 and retries == 0:
|
|
876
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
877
|
-
self.authenticate(revalidate=True)
|
|
878
|
-
retries += 1
|
|
879
|
-
else:
|
|
880
|
-
logger.error(
|
|
881
|
-
"Failed to add license feature -> %s associated with resource -> %s to partition -> %s; error -> %s (%s)",
|
|
882
|
-
license_feature,
|
|
883
|
-
resource_id,
|
|
884
|
-
partition_name,
|
|
885
|
-
response.text,
|
|
886
|
-
response.status_code,
|
|
887
|
-
)
|
|
888
|
-
return False
|
|
889
|
-
|
|
890
849
|
# end method definition
|
|
891
850
|
|
|
892
|
-
def
|
|
893
|
-
self,
|
|
894
|
-
resource_id: str,
|
|
895
|
-
license_feature: str,
|
|
896
|
-
license_name: str,
|
|
851
|
+
def update_user(
|
|
852
|
+
self, partition: str, user_id: str, attribute_name: str, attribute_value: str
|
|
897
853
|
) -> dict | None:
|
|
898
|
-
"""
|
|
899
|
-
associated with an OTDS resource (like "cs").
|
|
854
|
+
"""Update a user attribute with a new value
|
|
900
855
|
|
|
901
856
|
Args:
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
Example return value:
|
|
909
|
-
{
|
|
910
|
-
'status': 0,
|
|
911
|
-
'displayString': 'Success',
|
|
912
|
-
'exceptions': None,
|
|
913
|
-
'retValue': 0,
|
|
914
|
-
'listGroupsResults': {'groups': [...], 'actualPageSize': 0, 'nextPageCookie': None, 'requestedPageSize': 250},
|
|
915
|
-
'listUsersResults': {'users': [...], 'actualPageSize': 53, 'nextPageCookie': None, 'requestedPageSize': 250},
|
|
916
|
-
'listUserPartitionResult': {'_userPartitions': [...], 'warningMessage': None, 'actualPageSize': 0, 'nextPageCookie': None, 'requestedPageSize': 250},
|
|
917
|
-
'version': 1
|
|
918
|
-
}
|
|
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.
|
|
919
863
|
"""
|
|
920
864
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
# '_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
932
|
-
# '_oTLicenseResourceName': 'cs',
|
|
933
|
-
# '_oTLicenseProduct': 'EXTENDED_ECM',
|
|
934
|
-
# 'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
935
|
-
# 'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
936
|
-
# 'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
937
|
-
# 'description': 'CS license',
|
|
938
|
-
# 'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
939
|
-
# }
|
|
940
|
-
for lic in licenses:
|
|
941
|
-
if lic["_oTLicenseProduct"] == license_name:
|
|
942
|
-
license_location = lic["location"]
|
|
943
|
-
|
|
944
|
-
try:
|
|
945
|
-
license_location
|
|
946
|
-
except UnboundLocalError:
|
|
947
|
-
logger.error(
|
|
948
|
-
"Cannot find license -> %s for resource -> %s",
|
|
949
|
-
license_name,
|
|
950
|
-
resource_id,
|
|
951
|
-
)
|
|
952
|
-
return False
|
|
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
|
+
}
|
|
953
875
|
|
|
954
|
-
request_url = (
|
|
955
|
-
self.license_url()
|
|
956
|
-
+ "/object/"
|
|
957
|
-
+ license_location
|
|
958
|
-
+ "?counter="
|
|
959
|
-
+ license_feature
|
|
960
|
-
)
|
|
876
|
+
request_url = self.users_url() + "/" + user_id
|
|
961
877
|
|
|
962
878
|
logger.debug(
|
|
963
|
-
"
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
879
|
+
"Update user -> '%s' attribute -> '%s' to value -> '%s'; calling -> %s",
|
|
880
|
+
user_id,
|
|
881
|
+
attribute_name,
|
|
882
|
+
attribute_value,
|
|
967
883
|
request_url,
|
|
968
884
|
)
|
|
969
885
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
)
|
|
978
|
-
if response.ok:
|
|
979
|
-
return self.parse_request_response(response)
|
|
980
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
981
|
-
elif response.status_code == 401 and retries == 0:
|
|
982
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
983
|
-
self.authenticate(revalidate=True)
|
|
984
|
-
retries += 1
|
|
985
|
-
else:
|
|
986
|
-
logger.error(
|
|
987
|
-
"Failed to get licensed objects for license -> %s and license feature -> %s associated with resource -> %s; error -> %s (%s)",
|
|
988
|
-
license_name,
|
|
989
|
-
license_feature,
|
|
990
|
-
resource_id,
|
|
991
|
-
response.text,
|
|
992
|
-
response.status_code,
|
|
993
|
-
)
|
|
994
|
-
return None
|
|
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),
|
|
892
|
+
)
|
|
995
893
|
|
|
996
894
|
# end method definition
|
|
997
895
|
|
|
998
|
-
def
|
|
999
|
-
|
|
1000
|
-
) -> bool:
|
|
1001
|
-
"""Check if a user is licensed for a license and license feature associated with a particular OTDS resource.
|
|
896
|
+
def delete_user(self, partition: str, user_id: str) -> bool:
|
|
897
|
+
"""Delete an existing user
|
|
1002
898
|
|
|
1003
899
|
Args:
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
1007
|
-
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
1008
|
-
|
|
900
|
+
partition (str): name of the partition
|
|
901
|
+
user_id (str): Id (= login name) of the user
|
|
1009
902
|
Returns:
|
|
1010
|
-
bool: True
|
|
903
|
+
bool: True = success, False = error
|
|
1011
904
|
"""
|
|
1012
905
|
|
|
1013
|
-
|
|
1014
|
-
resource_id=resource_id,
|
|
1015
|
-
license_feature=license_feature,
|
|
1016
|
-
license_name=license_name,
|
|
1017
|
-
)
|
|
1018
|
-
|
|
1019
|
-
if not response or not response["listUsersResults"]:
|
|
1020
|
-
return False
|
|
1021
|
-
|
|
1022
|
-
users = response["listUsersResults"]["users"]
|
|
906
|
+
request_url = self.users_url() + "/" + user_id + "@" + partition
|
|
1023
907
|
|
|
1024
|
-
|
|
1025
|
-
|
|
908
|
+
logger.debug(
|
|
909
|
+
"Delete user -> '%s' in partition -> '%s'; calling -> %s",
|
|
910
|
+
user_id,
|
|
911
|
+
partition,
|
|
912
|
+
request_url,
|
|
913
|
+
)
|
|
1026
914
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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,
|
|
1030
921
|
)
|
|
1031
922
|
|
|
1032
|
-
if
|
|
923
|
+
if response and response.ok:
|
|
1033
924
|
return True
|
|
1034
925
|
|
|
1035
926
|
return False
|
|
1036
927
|
|
|
1037
928
|
# end method definition
|
|
1038
929
|
|
|
1039
|
-
def
|
|
1040
|
-
|
|
1041
|
-
) -> bool:
|
|
1042
|
-
"""Check if a group 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
|
|
1043
932
|
|
|
1044
933
|
Args:
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
|
|
1048
|
-
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
1049
|
-
|
|
934
|
+
user_id (str): Id (= login name) of the user
|
|
935
|
+
password (str): new password of the user
|
|
1050
936
|
Returns:
|
|
1051
|
-
bool: True
|
|
937
|
+
bool: True = success, False = error.
|
|
1052
938
|
"""
|
|
1053
939
|
|
|
1054
|
-
|
|
1055
|
-
resource_id=resource_id,
|
|
1056
|
-
license_feature=license_feature,
|
|
1057
|
-
license_name=license_name,
|
|
1058
|
-
)
|
|
1059
|
-
|
|
1060
|
-
if not response or not response["listGroupsResults"]:
|
|
1061
|
-
return False
|
|
940
|
+
user_post_body_json = {"newPassword": password}
|
|
1062
941
|
|
|
1063
|
-
|
|
942
|
+
request_url = "{}/{}/password".format(self.users_url(), user_id)
|
|
1064
943
|
|
|
1065
|
-
|
|
1066
|
-
|
|
944
|
+
logger.debug(
|
|
945
|
+
"Resetting password for user -> '%s'; calling -> %s", user_id, request_url
|
|
946
|
+
)
|
|
1067
947
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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,
|
|
1071
955
|
)
|
|
1072
956
|
|
|
1073
|
-
if
|
|
957
|
+
if response and response.ok:
|
|
1074
958
|
return True
|
|
1075
959
|
|
|
1076
960
|
return False
|
|
1077
961
|
|
|
1078
962
|
# end method definition
|
|
1079
963
|
|
|
1080
|
-
def
|
|
1081
|
-
|
|
1082
|
-
partition_name: str,
|
|
1083
|
-
resource_id: str,
|
|
1084
|
-
license_feature: str,
|
|
1085
|
-
license_name: str,
|
|
1086
|
-
) -> bool:
|
|
1087
|
-
"""Check if a partition 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
|
|
1088
966
|
|
|
1089
967
|
Args:
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
|
|
1094
|
-
|
|
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
|
|
1095
971
|
Returns:
|
|
1096
|
-
|
|
972
|
+
dict: Request response (json) or None if the creation fails.
|
|
1097
973
|
"""
|
|
1098
974
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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,
|
|
1103
988
|
)
|
|
989
|
+
logger.debug("Group Attributes -> %s", str(group_post_body_json))
|
|
1104
990
|
|
|
1105
|
-
|
|
1106
|
-
|
|
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
|
+
)
|
|
1107
998
|
|
|
1108
|
-
|
|
999
|
+
# end method definition
|
|
1109
1000
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1001
|
+
def get_group(self, group: str, show_error: bool = True) -> dict | None:
|
|
1002
|
+
"""Get a OTDS group by its group name
|
|
1112
1003
|
|
|
1113
|
-
|
|
1114
|
-
(
|
|
1115
|
-
|
|
1116
|
-
|
|
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
|
+
"""
|
|
1117
1026
|
|
|
1118
|
-
|
|
1119
|
-
return True
|
|
1027
|
+
request_url = self.groups_url() + "/" + group
|
|
1120
1028
|
|
|
1121
|
-
|
|
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
|
+
)
|
|
1122
1038
|
|
|
1123
1039
|
# end method definition
|
|
1124
1040
|
|
|
1125
|
-
def
|
|
1126
|
-
"""Add
|
|
1041
|
+
def add_user_to_group(self, user: str, group: str) -> bool:
|
|
1042
|
+
"""Add an existing user to an existing group in OTDS
|
|
1127
1043
|
|
|
1128
1044
|
Args:
|
|
1129
|
-
|
|
1130
|
-
|
|
1045
|
+
user (str): name of the OTDS user (needs to exist)
|
|
1046
|
+
group (str): name of the OTDS group (needs to exist)
|
|
1131
1047
|
Returns:
|
|
1132
|
-
|
|
1048
|
+
bool: True, if request is successful, False otherwise.
|
|
1133
1049
|
"""
|
|
1134
1050
|
|
|
1135
|
-
|
|
1051
|
+
user_to_group_post_body_json = {"stringList": [group]}
|
|
1136
1052
|
|
|
1137
|
-
request_url = self.
|
|
1053
|
+
request_url = self.users_url() + "/" + user + "/memberof"
|
|
1138
1054
|
|
|
1139
1055
|
logger.debug(
|
|
1140
|
-
"Adding user
|
|
1141
|
-
|
|
1142
|
-
|
|
1056
|
+
"Adding user -> '%s' to group -> '%s'; calling -> %s",
|
|
1057
|
+
user,
|
|
1058
|
+
group,
|
|
1143
1059
|
request_url,
|
|
1144
1060
|
)
|
|
1145
1061
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
)
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
else:
|
|
1163
|
-
logger.error(
|
|
1164
|
-
"Failed to add user partition -> %s; error -> %s (%s)",
|
|
1165
|
-
name,
|
|
1166
|
-
response.text,
|
|
1167
|
-
response.status_code,
|
|
1168
|
-
)
|
|
1169
|
-
return None
|
|
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,
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
if response and response.ok:
|
|
1075
|
+
return True
|
|
1076
|
+
|
|
1077
|
+
return False
|
|
1170
1078
|
|
|
1171
1079
|
# end method definition
|
|
1172
1080
|
|
|
1173
|
-
def
|
|
1174
|
-
"""
|
|
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
|
|
1175
1083
|
|
|
1176
1084
|
Args:
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
if partion is not found
|
|
1085
|
+
group (str): name of the OTDS group (needs to exist)
|
|
1086
|
+
parent_group (str): name of the OTDS parent group (needs to exist)
|
|
1180
1087
|
Returns:
|
|
1181
|
-
|
|
1088
|
+
bool: True, if request is successful, False otherwise.
|
|
1182
1089
|
"""
|
|
1183
1090
|
|
|
1184
|
-
|
|
1091
|
+
group_to_parent_group_post_body_json = {"stringList": [parent_group]}
|
|
1185
1092
|
|
|
1186
|
-
|
|
1093
|
+
request_url = self.groups_url() + "/" + group + "/memberof"
|
|
1187
1094
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
return None
|
|
1095
|
+
logger.debug(
|
|
1096
|
+
"Adding group -> '%s' to parent group -> '%s'; calling -> %s",
|
|
1097
|
+
group,
|
|
1098
|
+
parent_group,
|
|
1099
|
+
request_url,
|
|
1100
|
+
)
|
|
1101
|
+
|
|
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
|
+
)
|
|
1113
|
+
|
|
1114
|
+
if response and response.ok:
|
|
1115
|
+
return True
|
|
1116
|
+
|
|
1117
|
+
return False
|
|
1212
1118
|
|
|
1213
1119
|
# end method definition
|
|
1214
1120
|
|
|
1215
|
-
def
|
|
1121
|
+
def add_resource(
|
|
1216
1122
|
self,
|
|
1217
|
-
partition: str,
|
|
1218
1123
|
name: str,
|
|
1219
1124
|
description: str = "",
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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,
|
|
1223
1130
|
) -> dict | None:
|
|
1224
|
-
"""Add
|
|
1131
|
+
"""Add an OTDS resource
|
|
1225
1132
|
|
|
1226
1133
|
Args:
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
last_name (str, optional): last name of the new user
|
|
1232
|
-
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
|
|
1233
1138
|
Returns:
|
|
1234
|
-
dict: Request response or None if the
|
|
1139
|
+
dict: Request response (dictionary) or None if the REST call fails.
|
|
1235
1140
|
"""
|
|
1236
1141
|
|
|
1237
|
-
|
|
1238
|
-
"
|
|
1239
|
-
"values": [
|
|
1240
|
-
{"name": "sn", "values": [last_name]},
|
|
1241
|
-
{"name": "givenName", "values": [first_name]},
|
|
1242
|
-
{"name": "mail", "values": [email]},
|
|
1243
|
-
],
|
|
1244
|
-
"name": name,
|
|
1142
|
+
resource_post_body = {
|
|
1143
|
+
"resourceName": name,
|
|
1245
1144
|
"description": description,
|
|
1145
|
+
"displayName": display_name,
|
|
1146
|
+
"allowImpersonation": allow_impersonation,
|
|
1246
1147
|
}
|
|
1247
1148
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
"Adding user -> %s to partition -> %s; calling -> %s",
|
|
1252
|
-
name,
|
|
1253
|
-
partition,
|
|
1254
|
-
request_url,
|
|
1255
|
-
)
|
|
1256
|
-
logger.debug("User Attributes -> %s", str(userPostBodyJson))
|
|
1257
|
-
|
|
1258
|
-
retries = 0
|
|
1259
|
-
while True:
|
|
1260
|
-
response = requests.post(
|
|
1261
|
-
url=request_url,
|
|
1262
|
-
json=userPostBodyJson,
|
|
1263
|
-
headers=REQUEST_HEADERS,
|
|
1264
|
-
cookies=self.cookie(),
|
|
1265
|
-
timeout=None,
|
|
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!"
|
|
1266
1152
|
)
|
|
1267
|
-
|
|
1268
|
-
return self.parse_request_response(response)
|
|
1269
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1270
|
-
elif response.status_code == 401 and retries == 0:
|
|
1271
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1272
|
-
self.authenticate(revalidate=True)
|
|
1273
|
-
retries += 1
|
|
1274
|
-
else:
|
|
1275
|
-
logger.error(
|
|
1276
|
-
"Failed to add user -> %s; error -> %s (%s)",
|
|
1277
|
-
name,
|
|
1278
|
-
response.text,
|
|
1279
|
-
response.status_code,
|
|
1280
|
-
)
|
|
1281
|
-
return None
|
|
1282
|
-
|
|
1283
|
-
# end method definition
|
|
1153
|
+
return None
|
|
1284
1154
|
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
|
1287
1163
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
dict: Request response or None if the user was not found.
|
|
1293
|
-
"""
|
|
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)
|
|
1294
1168
|
|
|
1295
|
-
request_url = self.
|
|
1169
|
+
request_url = self.config()["resourceUrl"]
|
|
1296
1170
|
|
|
1297
1171
|
logger.debug(
|
|
1298
|
-
"
|
|
1299
|
-
|
|
1300
|
-
|
|
1172
|
+
"Adding resource -> '%s' ('%s'); calling -> %s",
|
|
1173
|
+
name,
|
|
1174
|
+
description,
|
|
1301
1175
|
request_url,
|
|
1302
1176
|
)
|
|
1303
1177
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
)
|
|
1312
|
-
if response.ok:
|
|
1313
|
-
return self.parse_request_response(response)
|
|
1314
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1315
|
-
elif response.status_code == 401 and retries == 0:
|
|
1316
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1317
|
-
self.authenticate(revalidate=True)
|
|
1318
|
-
retries += 1
|
|
1319
|
-
else:
|
|
1320
|
-
logger.error(
|
|
1321
|
-
"Failed to get user -> %s; error -> %s (%s)",
|
|
1322
|
-
user_id,
|
|
1323
|
-
response.text,
|
|
1324
|
-
response.status_code,
|
|
1325
|
-
)
|
|
1326
|
-
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
|
+
)
|
|
1327
1185
|
|
|
1328
1186
|
# end method definition
|
|
1329
1187
|
|
|
1330
|
-
def
|
|
1331
|
-
"""Get
|
|
1188
|
+
def get_resource(self, name: str, show_error: bool = False) -> dict | None:
|
|
1189
|
+
"""Get an existing OTDS resource
|
|
1332
1190
|
|
|
1333
1191
|
Args:
|
|
1334
|
-
|
|
1335
|
-
|
|
1192
|
+
name (str): name of the new OTDS resource
|
|
1193
|
+
show_error (bool, optional): treat as error if resource is not found
|
|
1336
1194
|
Returns:
|
|
1337
|
-
dict: Request response or None if the
|
|
1338
|
-
"""
|
|
1339
|
-
|
|
1340
|
-
# Add query parameters (these are NOT passed via JSon body!)
|
|
1341
|
-
query = {}
|
|
1342
|
-
if limit:
|
|
1343
|
-
query["limit"] = limit
|
|
1344
|
-
if partition:
|
|
1345
|
-
query["where_partition_name"] = partition
|
|
1346
|
-
|
|
1347
|
-
encodedQuery = urllib.parse.urlencode(query, doseq=True)
|
|
1348
|
-
|
|
1349
|
-
request_url = self.users_url()
|
|
1350
|
-
if query:
|
|
1351
|
-
request_url += "?{}".format(encodedQuery)
|
|
1352
|
-
|
|
1353
|
-
if partition:
|
|
1354
|
-
logger.debug(
|
|
1355
|
-
"Get all users in partition -> %s (limit -> %s); calling -> %s",
|
|
1356
|
-
partition,
|
|
1357
|
-
limit,
|
|
1358
|
-
request_url,
|
|
1359
|
-
)
|
|
1360
|
-
else:
|
|
1361
|
-
logger.debug(
|
|
1362
|
-
"Get all users (limit -> %s); calling -> %s",
|
|
1363
|
-
limit,
|
|
1364
|
-
request_url,
|
|
1365
|
-
)
|
|
1195
|
+
dict: Request response or None if the REST call fails.
|
|
1366
1196
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
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
|
+
}
|
|
1226
|
+
"""
|
|
1227
|
+
|
|
1228
|
+
request_url = "{}/{}".format(self.config()["resourceUrl"], name)
|
|
1229
|
+
|
|
1230
|
+
logger.debug("Get resource -> '%s'; calling -> %s", name, request_url)
|
|
1231
|
+
|
|
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
|
+
)
|
|
1397
1239
|
|
|
1398
1240
|
# end method definition
|
|
1399
1241
|
|
|
1400
|
-
def
|
|
1401
|
-
self,
|
|
1242
|
+
def update_resource(
|
|
1243
|
+
self, name: str, resource: object, show_error: bool = True
|
|
1402
1244
|
) -> dict | None:
|
|
1403
|
-
"""Update
|
|
1245
|
+
"""Update an existing OTDS resource
|
|
1404
1246
|
|
|
1405
1247
|
Args:
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
dict: Request response or None if the update fails.
|
|
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
|
|
1251
|
+
Returns:
|
|
1252
|
+
dict: Request response (json) or None if the REST call fails.
|
|
1412
1253
|
"""
|
|
1413
1254
|
|
|
1414
|
-
|
|
1415
|
-
userPatchBodyJson = {
|
|
1416
|
-
"userPartitionID": partition,
|
|
1417
|
-
attribute_name: attribute_value,
|
|
1418
|
-
}
|
|
1419
|
-
else:
|
|
1420
|
-
userPatchBodyJson = {
|
|
1421
|
-
"userPartitionID": partition,
|
|
1422
|
-
"values": [{"name": attribute_name, "values": [attribute_value]}],
|
|
1423
|
-
}
|
|
1255
|
+
request_url = "{}/{}".format(self.config()["resourceUrl"], name)
|
|
1424
1256
|
|
|
1425
|
-
|
|
1257
|
+
logger.debug("Updating resource -> '%s'; calling -> %s", name, request_url)
|
|
1426
1258
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
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,
|
|
1433
1266
|
)
|
|
1434
1267
|
|
|
1435
|
-
retries = 0
|
|
1436
|
-
while True:
|
|
1437
|
-
response = requests.patch(
|
|
1438
|
-
url=request_url,
|
|
1439
|
-
json=userPatchBodyJson,
|
|
1440
|
-
headers=REQUEST_HEADERS,
|
|
1441
|
-
cookies=self.cookie(),
|
|
1442
|
-
timeout=None,
|
|
1443
|
-
)
|
|
1444
|
-
if response.ok:
|
|
1445
|
-
return self.parse_request_response(response)
|
|
1446
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1447
|
-
elif response.status_code == 401 and retries == 0:
|
|
1448
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1449
|
-
self.authenticate(revalidate=True)
|
|
1450
|
-
retries += 1
|
|
1451
|
-
elif response.status_code == 404:
|
|
1452
|
-
logger.warning("User does not exist -> %s", user_id)
|
|
1453
|
-
return None
|
|
1454
|
-
else:
|
|
1455
|
-
logger.error(
|
|
1456
|
-
"Failed to update user -> %s; error -> %s (%s)",
|
|
1457
|
-
user_id,
|
|
1458
|
-
response.text,
|
|
1459
|
-
response.status_code,
|
|
1460
|
-
)
|
|
1461
|
-
return None
|
|
1462
|
-
|
|
1463
1268
|
# end method definition
|
|
1464
1269
|
|
|
1465
|
-
def
|
|
1466
|
-
"""
|
|
1270
|
+
def activate_resource(self, resource_id: str) -> dict | None:
|
|
1271
|
+
"""Activate an OTDS resource
|
|
1467
1272
|
|
|
1468
1273
|
Args:
|
|
1469
|
-
|
|
1470
|
-
user_id (str): Id (= login name) of the user
|
|
1274
|
+
resource_id (str): ID of the OTDS resource
|
|
1471
1275
|
Returns:
|
|
1472
|
-
|
|
1276
|
+
dict: Request response (json) or None if the REST call fails.
|
|
1473
1277
|
"""
|
|
1474
1278
|
|
|
1475
|
-
|
|
1279
|
+
resource_post_body_json = {}
|
|
1280
|
+
|
|
1281
|
+
request_url = "{}/{}/activate".format(self.config()["resourceUrl"], resource_id)
|
|
1476
1282
|
|
|
1477
1283
|
logger.debug(
|
|
1478
|
-
"
|
|
1479
|
-
user_id,
|
|
1480
|
-
partition,
|
|
1481
|
-
request_url,
|
|
1284
|
+
"Activating resource -> '%s'; calling -> %s", resource_id, request_url
|
|
1482
1285
|
)
|
|
1483
1286
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
)
|
|
1492
|
-
if response.ok:
|
|
1493
|
-
return True
|
|
1494
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1495
|
-
elif response.status_code == 401 and retries == 0:
|
|
1496
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1497
|
-
self.authenticate(revalidate=True)
|
|
1498
|
-
retries += 1
|
|
1499
|
-
else:
|
|
1500
|
-
logger.error(
|
|
1501
|
-
"Failed to delete user -> %s; error -> %s (%s)",
|
|
1502
|
-
user_id,
|
|
1503
|
-
response.text,
|
|
1504
|
-
response.status_code,
|
|
1505
|
-
)
|
|
1506
|
-
return False
|
|
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
|
+
)
|
|
1507
1294
|
|
|
1508
1295
|
# end method definition
|
|
1509
1296
|
|
|
1510
|
-
def
|
|
1511
|
-
"""
|
|
1297
|
+
def get_access_roles(self) -> dict | None:
|
|
1298
|
+
"""Get a list of all OTDS access roles
|
|
1512
1299
|
|
|
1513
1300
|
Args:
|
|
1514
|
-
|
|
1515
|
-
password (str): new password of the user
|
|
1301
|
+
None
|
|
1516
1302
|
Returns:
|
|
1517
|
-
|
|
1303
|
+
dict: Request response or None if the REST call fails.
|
|
1518
1304
|
"""
|
|
1519
1305
|
|
|
1520
|
-
|
|
1306
|
+
request_url = self.config()["accessRoleUrl"]
|
|
1521
1307
|
|
|
1522
|
-
|
|
1308
|
+
logger.debug("Retrieving access roles; calling -> %s", request_url)
|
|
1523
1309
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1310
|
+
return self.do_request(
|
|
1311
|
+
url=request_url,
|
|
1312
|
+
method="GET",
|
|
1313
|
+
timeout=None,
|
|
1314
|
+
failure_message="Failed to get access roles",
|
|
1526
1315
|
)
|
|
1527
1316
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
)
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
response.status_code,
|
|
1550
|
-
)
|
|
1551
|
-
return False
|
|
1317
|
+
# end method definition
|
|
1318
|
+
|
|
1319
|
+
def get_access_role(self, access_role: str) -> dict | None:
|
|
1320
|
+
"""Get an OTDS access role
|
|
1321
|
+
|
|
1322
|
+
Args:
|
|
1323
|
+
name (str): name of the access role
|
|
1324
|
+
Returns:
|
|
1325
|
+
dict: Request response (json) or None if the REST call fails.
|
|
1326
|
+
"""
|
|
1327
|
+
|
|
1328
|
+
request_url = self.config()["accessRoleUrl"] + "/" + access_role
|
|
1329
|
+
|
|
1330
|
+
logger.debug("Get access role -> '%s'; calling -> %s", access_role, request_url)
|
|
1331
|
+
|
|
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),
|
|
1337
|
+
)
|
|
1552
1338
|
|
|
1553
1339
|
# end method definition
|
|
1554
1340
|
|
|
1555
|
-
def
|
|
1556
|
-
|
|
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
|
|
1557
1345
|
|
|
1558
1346
|
Args:
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
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 ("")
|
|
1562
1351
|
Returns:
|
|
1563
|
-
|
|
1352
|
+
bool: True if partition is in access role or has been successfully added.
|
|
1353
|
+
False if partition has been not been added (error)
|
|
1564
1354
|
"""
|
|
1565
1355
|
|
|
1566
|
-
|
|
1567
|
-
"
|
|
1568
|
-
"name": name,
|
|
1569
|
-
"description": description,
|
|
1356
|
+
access_role_post_body_json = {
|
|
1357
|
+
"userPartitions": [{"name": partition, "location": location}]
|
|
1570
1358
|
}
|
|
1571
1359
|
|
|
1572
|
-
request_url =
|
|
1360
|
+
request_url = "{}/{}/members".format(
|
|
1361
|
+
self.config()["accessRoleUrl"], access_role
|
|
1362
|
+
)
|
|
1573
1363
|
|
|
1574
1364
|
logger.debug(
|
|
1575
|
-
"
|
|
1576
|
-
name,
|
|
1365
|
+
"Add user partition -> '%s' to access role -> '%s'; calling -> %s",
|
|
1577
1366
|
partition,
|
|
1367
|
+
access_role,
|
|
1578
1368
|
request_url,
|
|
1579
1369
|
)
|
|
1580
|
-
logger.debug("Group Attributes -> %s", str(groupPostBodyJson))
|
|
1581
1370
|
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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
|
|
1386
|
+
|
|
1387
|
+
# end method definition
|
|
1388
|
+
|
|
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
|
|
1393
|
+
|
|
1394
|
+
Args:
|
|
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)
|
|
1402
|
+
"""
|
|
1403
|
+
|
|
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
|
|
1408
|
+
|
|
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,
|
|
1604
1417
|
)
|
|
1605
|
-
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
|
|
1606
1457
|
|
|
1607
1458
|
# end method definition
|
|
1608
1459
|
|
|
1609
|
-
def
|
|
1610
|
-
|
|
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
|
|
1611
1464
|
|
|
1612
1465
|
Args:
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
'name': 'Sales',
|
|
1621
|
-
'location': 'oTGroup=3f921294-b92a-4c9e-bf7c-b50df16bb937,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net',
|
|
1622
|
-
'id': 'Sales@Content Server Members',
|
|
1623
|
-
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...],
|
|
1624
|
-
'description': None,
|
|
1625
|
-
'uuid': '3f921294-b92a-4c9e-bf7c-b50df16bb937',
|
|
1626
|
-
'objectClass': 'oTGroup',
|
|
1627
|
-
'customAttributes': None,
|
|
1628
|
-
'originUUID': None,
|
|
1629
|
-
'urlId': 'Sales@Content Server Members',
|
|
1630
|
-
'urlLocation': 'oTGroup=3f921294-b92a-4c9e-bf7c-b50df16bb937,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net'
|
|
1631
|
-
}
|
|
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 ("")
|
|
1470
|
+
Returns:
|
|
1471
|
+
bool: True if group is in access role or has been successfully added.
|
|
1472
|
+
False if group has been not been added (error)
|
|
1632
1473
|
"""
|
|
1633
1474
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
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
|
|
1637
1479
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
timeout=None,
|
|
1645
|
-
)
|
|
1646
|
-
if response.ok:
|
|
1647
|
-
return self.parse_request_response(response)
|
|
1648
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1649
|
-
elif response.status_code == 401 and retries == 0:
|
|
1650
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1651
|
-
self.authenticate(revalidate=True)
|
|
1652
|
-
retries += 1
|
|
1653
|
-
else:
|
|
1654
|
-
logger.error(
|
|
1655
|
-
"Failed to get group -> %s; error -> %s (%s)",
|
|
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'",
|
|
1656
1486
|
group,
|
|
1657
|
-
|
|
1658
|
-
response.status_code,
|
|
1487
|
+
access_role,
|
|
1659
1488
|
)
|
|
1660
|
-
return
|
|
1661
|
-
|
|
1662
|
-
# end method definition
|
|
1663
|
-
|
|
1664
|
-
def add_user_to_group(self, user: str, group: str) -> bool:
|
|
1665
|
-
"""Add an existing user to an existing group in OTDS
|
|
1489
|
+
return True
|
|
1666
1490
|
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
group
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
"""
|
|
1491
|
+
logger.debug(
|
|
1492
|
+
"Group -> '%s' is not yet in access role -> '%s' - adding...",
|
|
1493
|
+
group,
|
|
1494
|
+
access_role,
|
|
1495
|
+
)
|
|
1673
1496
|
|
|
1674
|
-
|
|
1497
|
+
# create payload for REST call:
|
|
1498
|
+
access_role_post_body_json = {"groups": [{"name": group, "location": location}]}
|
|
1675
1499
|
|
|
1676
|
-
request_url =
|
|
1500
|
+
request_url = "{}/{}/members".format(
|
|
1501
|
+
self.config()["accessRoleUrl"], access_role
|
|
1502
|
+
)
|
|
1677
1503
|
|
|
1678
1504
|
logger.debug(
|
|
1679
|
-
"
|
|
1505
|
+
"Add group -> '%s' to access role -> '%s'; calling -> %s",
|
|
1506
|
+
group,
|
|
1507
|
+
access_role,
|
|
1508
|
+
request_url,
|
|
1680
1509
|
)
|
|
1681
1510
|
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
retries += 1
|
|
1698
|
-
else:
|
|
1699
|
-
logger.error(
|
|
1700
|
-
"Failed to add user -> %s to group -> %s; error -> %s (%s)",
|
|
1701
|
-
user,
|
|
1702
|
-
group,
|
|
1703
|
-
response.text,
|
|
1704
|
-
response.status_code,
|
|
1705
|
-
)
|
|
1706
|
-
return False
|
|
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
|
|
1707
1526
|
|
|
1708
1527
|
# end method definition
|
|
1709
1528
|
|
|
1710
|
-
def
|
|
1711
|
-
|
|
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
|
|
1712
1533
|
|
|
1713
1534
|
Args:
|
|
1714
|
-
|
|
1715
|
-
|
|
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"]}]
|
|
1716
1539
|
Returns:
|
|
1717
|
-
|
|
1540
|
+
dict: Request response (json) or None if the REST call fails.
|
|
1718
1541
|
"""
|
|
1719
1542
|
|
|
1720
|
-
|
|
1543
|
+
# Return if list is empty:
|
|
1544
|
+
if not attribute_list:
|
|
1545
|
+
return None
|
|
1721
1546
|
|
|
1722
|
-
|
|
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
|
|
1552
|
+
|
|
1553
|
+
access_role_put_body_json = {"attributes": attribute_list}
|
|
1554
|
+
|
|
1555
|
+
request_url = "{}/{}/attributes".format(self.config()["accessRoleUrl"], name)
|
|
1723
1556
|
|
|
1724
1557
|
logger.debug(
|
|
1725
|
-
"
|
|
1726
|
-
|
|
1727
|
-
|
|
1558
|
+
"Update access role -> '%s' with attributes -> %s; calling -> %s",
|
|
1559
|
+
name,
|
|
1560
|
+
str(access_role_put_body_json),
|
|
1728
1561
|
request_url,
|
|
1729
1562
|
)
|
|
1730
1563
|
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
timeout=None,
|
|
1739
|
-
)
|
|
1740
|
-
|
|
1741
|
-
if response.ok:
|
|
1742
|
-
return True
|
|
1743
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1744
|
-
elif response.status_code == 401 and retries == 0:
|
|
1745
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1746
|
-
self.authenticate(revalidate=True)
|
|
1747
|
-
retries += 1
|
|
1748
|
-
else:
|
|
1749
|
-
logger.error(
|
|
1750
|
-
"Failed to add group -> %s to parent group -> %s; error -> %s (%s)",
|
|
1751
|
-
group,
|
|
1752
|
-
parent_group,
|
|
1753
|
-
response.text,
|
|
1754
|
-
response.status_code,
|
|
1755
|
-
)
|
|
1756
|
-
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
|
+
)
|
|
1757
1571
|
|
|
1758
1572
|
# end method definition
|
|
1759
1573
|
|
|
1760
|
-
def
|
|
1574
|
+
def add_license_to_resource(
|
|
1761
1575
|
self,
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1576
|
+
path_to_license_file: str,
|
|
1577
|
+
product_name: str,
|
|
1578
|
+
product_description: str,
|
|
1579
|
+
resource_id: str,
|
|
1580
|
+
update: bool = True,
|
|
1766
1581
|
) -> dict | None:
|
|
1767
|
-
"""Add an OTDS resource
|
|
1582
|
+
"""Add a product license to an OTDS resource.
|
|
1768
1583
|
|
|
1769
1584
|
Args:
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
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)
|
|
1774
1590
|
Returns:
|
|
1775
|
-
dict: Request response (dictionary) or None if the REST call fails
|
|
1591
|
+
dict: Request response (dictionary) or None if the REST call fails
|
|
1776
1592
|
"""
|
|
1777
1593
|
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
"
|
|
1781
|
-
|
|
1782
|
-
|
|
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
|
|
1783
1605
|
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
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
|
+
}
|
|
1788
1615
|
|
|
1789
|
-
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
|
|
1790
1629
|
|
|
1791
1630
|
logger.debug(
|
|
1792
|
-
"Adding
|
|
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,
|
|
1793
1636
|
)
|
|
1794
1637
|
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1638
|
+
if update:
|
|
1639
|
+
# Do a REST PUT call for update an existing license:
|
|
1640
|
+
return self.do_request(
|
|
1798
1641
|
url=request_url,
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
cookies=self.cookie(),
|
|
1642
|
+
method="PUT",
|
|
1643
|
+
json_data=license_post_body_json,
|
|
1802
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
|
+
),
|
|
1803
1659
|
)
|
|
1804
|
-
if response.ok:
|
|
1805
|
-
return self.parse_request_response(response)
|
|
1806
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1807
|
-
elif response.status_code == 401 and retries == 0:
|
|
1808
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1809
|
-
self.authenticate(revalidate=True)
|
|
1810
|
-
retries += 1
|
|
1811
|
-
else:
|
|
1812
|
-
logger.error(
|
|
1813
|
-
"Failed to add resource -> %s; error -> %s (%s)",
|
|
1814
|
-
name,
|
|
1815
|
-
response.text,
|
|
1816
|
-
response.status_code,
|
|
1817
|
-
)
|
|
1818
|
-
return None
|
|
1819
1660
|
|
|
1820
1661
|
# end method definition
|
|
1821
1662
|
|
|
1822
|
-
def
|
|
1823
|
-
"""Get
|
|
1663
|
+
def get_license_for_resource(self, resource_id: str):
|
|
1664
|
+
"""Get a product license for a resource in OTDS.
|
|
1824
1665
|
|
|
1825
1666
|
Args:
|
|
1826
|
-
|
|
1827
|
-
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!)
|
|
1828
1668
|
Returns:
|
|
1829
|
-
|
|
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
|
+
}
|
|
1830
1683
|
"""
|
|
1831
1684
|
|
|
1832
|
-
request_url =
|
|
1685
|
+
request_url = (
|
|
1686
|
+
self.license_url()
|
|
1687
|
+
+ "/assignedlicenses?resourceID="
|
|
1688
|
+
+ resource_id
|
|
1689
|
+
+ "&validOnly=false"
|
|
1690
|
+
)
|
|
1833
1691
|
|
|
1834
|
-
logger.debug(
|
|
1692
|
+
logger.debug(
|
|
1693
|
+
"Get license for resource -> %s; calling -> %s", resource_id, request_url
|
|
1694
|
+
)
|
|
1835
1695
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
self.authenticate(revalidate=True)
|
|
1850
|
-
retries += 1
|
|
1851
|
-
else:
|
|
1852
|
-
# We don't necessarily want to log an error as this function
|
|
1853
|
-
# is also used in wait loops:
|
|
1854
|
-
if show_error:
|
|
1855
|
-
logger.warning(
|
|
1856
|
-
"Failed to retrieve resource -> %s; warning -> %s",
|
|
1857
|
-
name,
|
|
1858
|
-
response.text,
|
|
1859
|
-
)
|
|
1860
|
-
else:
|
|
1861
|
-
logger.debug("Resource -> %s not found.", name)
|
|
1862
|
-
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"]
|
|
1863
1709
|
|
|
1864
1710
|
# end method definition
|
|
1865
1711
|
|
|
1866
|
-
def
|
|
1867
|
-
|
|
1868
|
-
) -> dict | None:
|
|
1869
|
-
"""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.
|
|
1870
1714
|
|
|
1871
1715
|
Args:
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
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!)
|
|
1875
1718
|
Returns:
|
|
1876
|
-
|
|
1719
|
+
bool: True if successful or False if the REST call fails
|
|
1877
1720
|
"""
|
|
1878
1721
|
|
|
1879
|
-
request_url = "{}/{}".format(self.
|
|
1722
|
+
request_url = "{}/{}".format(self.license_url(), license_id)
|
|
1880
1723
|
|
|
1881
|
-
logger.debug(
|
|
1724
|
+
logger.debug(
|
|
1725
|
+
"Deleting product license -> '%s' from resource -> '%s'; calling -> %s",
|
|
1726
|
+
license_id,
|
|
1727
|
+
resource_id,
|
|
1728
|
+
request_url,
|
|
1729
|
+
)
|
|
1882
1730
|
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
self.authenticate(revalidate=True)
|
|
1898
|
-
retries += 1
|
|
1899
|
-
else:
|
|
1900
|
-
# We don't necessarily want to log an error as this function
|
|
1901
|
-
# is also used in wait loops:
|
|
1902
|
-
if show_error:
|
|
1903
|
-
logger.warning(
|
|
1904
|
-
"Failed to retrieve resource -> %s; warning -> %s",
|
|
1905
|
-
name,
|
|
1906
|
-
response.text,
|
|
1907
|
-
)
|
|
1908
|
-
else:
|
|
1909
|
-
logger.debug("Resource -> %s not found.", name)
|
|
1910
|
-
return None
|
|
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
|
|
1911
1745
|
|
|
1912
1746
|
# end method definition
|
|
1913
1747
|
|
|
1914
|
-
def
|
|
1915
|
-
|
|
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.
|
|
1916
1758
|
|
|
1917
1759
|
Args:
|
|
1918
|
-
|
|
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"
|
|
1919
1766
|
Returns:
|
|
1920
|
-
|
|
1767
|
+
bool: True if successful or False if the REST call fails or the license is not found
|
|
1921
1768
|
"""
|
|
1922
1769
|
|
|
1923
|
-
|
|
1770
|
+
licenses = self.get_license_for_resource(resource_id)
|
|
1771
|
+
|
|
1772
|
+
for lic in licenses:
|
|
1773
|
+
if lic["_oTLicenseProduct"] == license_name:
|
|
1774
|
+
license_location = lic["id"]
|
|
1924
1775
|
|
|
1925
|
-
|
|
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
|
|
1926
1801
|
|
|
1927
1802
|
logger.debug(
|
|
1928
|
-
"
|
|
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,
|
|
1929
1809
|
)
|
|
1930
1810
|
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
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,
|
|
1939
1827
|
)
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
elif response.status_code == 401 and retries == 0:
|
|
1944
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1945
|
-
self.authenticate(revalidate=True)
|
|
1946
|
-
retries += 1
|
|
1947
|
-
else:
|
|
1948
|
-
logger.error(
|
|
1949
|
-
"Failed to activate resource -> %s; error -> %s (%s)",
|
|
1950
|
-
resource_id,
|
|
1951
|
-
response.text,
|
|
1952
|
-
response.status_code,
|
|
1953
|
-
)
|
|
1954
|
-
return None
|
|
1828
|
+
return True
|
|
1829
|
+
|
|
1830
|
+
return False
|
|
1955
1831
|
|
|
1956
1832
|
# end method definition
|
|
1957
1833
|
|
|
1958
|
-
def
|
|
1959
|
-
|
|
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).
|
|
1960
1843
|
|
|
1961
1844
|
Args:
|
|
1962
|
-
|
|
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"
|
|
1963
1850
|
Returns:
|
|
1964
|
-
|
|
1851
|
+
bool: True if successful or False if the REST call fails or the license is not found
|
|
1965
1852
|
"""
|
|
1966
1853
|
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
while True:
|
|
1973
|
-
response = requests.get(
|
|
1974
|
-
url=request_url,
|
|
1975
|
-
headers=REQUEST_HEADERS,
|
|
1976
|
-
cookies=self.cookie(),
|
|
1977
|
-
timeout=None,
|
|
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,
|
|
1978
1859
|
)
|
|
1979
|
-
|
|
1980
|
-
return self.parse_request_response(response)
|
|
1981
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
1982
|
-
elif response.status_code == 401 and retries == 0:
|
|
1983
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1984
|
-
self.authenticate(revalidate=True)
|
|
1985
|
-
retries += 1
|
|
1986
|
-
else:
|
|
1987
|
-
logger.error(
|
|
1988
|
-
"Failed to retrieve access roles; error -> %s (%s)",
|
|
1989
|
-
response.text,
|
|
1990
|
-
response.status_code,
|
|
1991
|
-
)
|
|
1992
|
-
return None
|
|
1860
|
+
return False
|
|
1993
1861
|
|
|
1994
|
-
|
|
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"]
|
|
1995
1877
|
|
|
1996
|
-
|
|
1997
|
-
|
|
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,
|
|
1885
|
+
)
|
|
1886
|
+
return False
|
|
1998
1887
|
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
1888
|
+
license_post_body_json = {
|
|
1889
|
+
"_oTLicenseType": license_type,
|
|
1890
|
+
"_oTLicenseProduct": "partitions",
|
|
1891
|
+
"name": partition_name,
|
|
1892
|
+
"values": [{"name": "counter", "values": [license_feature]}],
|
|
1893
|
+
}
|
|
2004
1894
|
|
|
2005
|
-
request_url = self.
|
|
1895
|
+
request_url = self.license_url() + "/object/" + license_location
|
|
2006
1896
|
|
|
2007
1897
|
logger.debug(
|
|
2008
|
-
"
|
|
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,
|
|
2009
1904
|
)
|
|
2010
1905
|
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
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,
|
|
2018
1922
|
)
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
elif response.status_code == 401 and retries == 0:
|
|
2023
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2024
|
-
self.authenticate(revalidate=True)
|
|
2025
|
-
retries += 1
|
|
2026
|
-
else:
|
|
2027
|
-
logger.error(
|
|
2028
|
-
"Failed to retrieve access role -> %s; error -> %s (%s)",
|
|
2029
|
-
access_role,
|
|
2030
|
-
response.text,
|
|
2031
|
-
response.status_code,
|
|
2032
|
-
)
|
|
2033
|
-
return None
|
|
1923
|
+
return True
|
|
1924
|
+
|
|
1925
|
+
return False
|
|
2034
1926
|
|
|
2035
1927
|
# end method definition
|
|
2036
1928
|
|
|
2037
|
-
def
|
|
2038
|
-
self,
|
|
2039
|
-
|
|
2040
|
-
|
|
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").
|
|
2041
1937
|
|
|
2042
1938
|
Args:
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
most of the times you will want to keep it to empty string ("")
|
|
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"
|
|
2047
1942
|
Returns:
|
|
2048
|
-
|
|
2049
|
-
|
|
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
|
+
}
|
|
2050
1956
|
"""
|
|
2051
1957
|
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
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
|
|
2055
1965
|
|
|
2056
|
-
|
|
2057
|
-
|
|
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"]
|
|
1981
|
+
|
|
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,
|
|
1989
|
+
)
|
|
1990
|
+
return False
|
|
1991
|
+
|
|
1992
|
+
request_url = (
|
|
1993
|
+
self.license_url()
|
|
1994
|
+
+ "/object/"
|
|
1995
|
+
+ license_location
|
|
1996
|
+
+ "?counter="
|
|
1997
|
+
+ license_feature
|
|
2058
1998
|
)
|
|
2059
1999
|
|
|
2060
2000
|
logger.debug(
|
|
2061
|
-
"
|
|
2062
|
-
|
|
2063
|
-
|
|
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,
|
|
2064
2005
|
request_url,
|
|
2065
2006
|
)
|
|
2066
2007
|
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
)
|
|
2076
|
-
if response.ok:
|
|
2077
|
-
return True
|
|
2078
|
-
elif response.status_code == 401 and retries == 0:
|
|
2079
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2080
|
-
self.authenticate(revalidate=True)
|
|
2081
|
-
retries += 1
|
|
2082
|
-
else:
|
|
2083
|
-
logger.error(
|
|
2084
|
-
"Failed to add partition -> %s to access role -> %s; error -> %s (%s)",
|
|
2085
|
-
partition,
|
|
2086
|
-
access_role,
|
|
2087
|
-
response.text,
|
|
2088
|
-
response.status_code,
|
|
2089
|
-
)
|
|
2090
|
-
return False
|
|
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
|
+
)
|
|
2091
2016
|
|
|
2092
2017
|
# end method definition
|
|
2093
2018
|
|
|
2094
|
-
def
|
|
2095
|
-
self,
|
|
2019
|
+
def is_user_licensed(
|
|
2020
|
+
self, user_name: str, resource_id: str, license_feature: str, license_name: str
|
|
2096
2021
|
) -> bool:
|
|
2097
|
-
"""
|
|
2022
|
+
"""Check if a user is licensed for a license and license feature associated with a particular OTDS resource.
|
|
2098
2023
|
|
|
2099
2024
|
Args:
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
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
|
+
|
|
2104
2030
|
Returns:
|
|
2105
|
-
bool: True if user is
|
|
2106
|
-
False if user has not been added (error)
|
|
2031
|
+
bool: True if the user is licensed and False otherwise
|
|
2107
2032
|
"""
|
|
2108
2033
|
|
|
2109
|
-
|
|
2110
|
-
|
|
2034
|
+
response = self.get_licensed_objects(
|
|
2035
|
+
resource_id=resource_id,
|
|
2036
|
+
license_feature=license_feature,
|
|
2037
|
+
license_name=license_name,
|
|
2038
|
+
)
|
|
2111
2039
|
|
|
2112
|
-
if not
|
|
2040
|
+
if not response or not response["listUsersResults"]:
|
|
2113
2041
|
return False
|
|
2114
2042
|
|
|
2115
|
-
|
|
2116
|
-
accessRoleUsers = accessRolesGetResponse["accessRoleMembers"]["users"]
|
|
2117
|
-
for user in accessRoleUsers:
|
|
2118
|
-
if user["displayName"] == user_id:
|
|
2119
|
-
logger.debug(
|
|
2120
|
-
"User -> %s already added to access role -> %s",
|
|
2121
|
-
user_id,
|
|
2122
|
-
access_role,
|
|
2123
|
-
)
|
|
2124
|
-
return True
|
|
2043
|
+
users = response["listUsersResults"]["users"]
|
|
2125
2044
|
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2045
|
+
if not users:
|
|
2046
|
+
return False
|
|
2047
|
+
|
|
2048
|
+
user = next(
|
|
2049
|
+
(item for item in users if item["name"] == user_name),
|
|
2050
|
+
None,
|
|
2130
2051
|
)
|
|
2131
2052
|
|
|
2132
|
-
|
|
2133
|
-
|
|
2053
|
+
if user:
|
|
2054
|
+
return True
|
|
2134
2055
|
|
|
2135
|
-
|
|
2136
|
-
self.config()["accessRoleUrl"], access_role
|
|
2137
|
-
)
|
|
2056
|
+
return False
|
|
2138
2057
|
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2058
|
+
# end method definition
|
|
2059
|
+
|
|
2060
|
+
def is_group_licensed(
|
|
2061
|
+
self, group_name: str, resource_id: str, license_feature: str, license_name: str
|
|
2062
|
+
) -> bool:
|
|
2063
|
+
"""Check if a group is licensed for a license and license feature associated with a particular OTDS resource.
|
|
2064
|
+
|
|
2065
|
+
Args:
|
|
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
|
+
|
|
2071
|
+
Returns:
|
|
2072
|
+
bool: True if the group is licensed and False otherwise
|
|
2073
|
+
"""
|
|
2074
|
+
|
|
2075
|
+
response = self.get_licensed_objects(
|
|
2076
|
+
resource_id=resource_id,
|
|
2077
|
+
license_feature=license_feature,
|
|
2078
|
+
license_name=license_name,
|
|
2144
2079
|
)
|
|
2145
2080
|
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
if
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
"Failed to add user -> %s to access role -> %s; error -> %s (%s)",
|
|
2164
|
-
user_id,
|
|
2165
|
-
access_role,
|
|
2166
|
-
response.text,
|
|
2167
|
-
response.status_code,
|
|
2168
|
-
)
|
|
2169
|
-
return False
|
|
2081
|
+
if not response or not response["listGroupsResults"]:
|
|
2082
|
+
return False
|
|
2083
|
+
|
|
2084
|
+
groups = response["listGroupsResults"]["groups"]
|
|
2085
|
+
|
|
2086
|
+
if not groups:
|
|
2087
|
+
return False
|
|
2088
|
+
|
|
2089
|
+
group = next(
|
|
2090
|
+
(item for item in groups if item["name"] == group_name),
|
|
2091
|
+
None,
|
|
2092
|
+
)
|
|
2093
|
+
|
|
2094
|
+
if group:
|
|
2095
|
+
return True
|
|
2096
|
+
|
|
2097
|
+
return False
|
|
2170
2098
|
|
|
2171
2099
|
# end method definition
|
|
2172
2100
|
|
|
2173
|
-
def
|
|
2174
|
-
self,
|
|
2101
|
+
def is_partition_licensed(
|
|
2102
|
+
self,
|
|
2103
|
+
partition_name: str,
|
|
2104
|
+
resource_id: str,
|
|
2105
|
+
license_feature: str,
|
|
2106
|
+
license_name: str,
|
|
2175
2107
|
) -> bool:
|
|
2176
|
-
"""
|
|
2108
|
+
"""Check if a partition is licensed for a license and license feature associated with a particular OTDS resource.
|
|
2177
2109
|
|
|
2178
2110
|
Args:
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
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
|
+
|
|
2183
2116
|
Returns:
|
|
2184
|
-
bool: True if
|
|
2185
|
-
False if group has been not been added (error)
|
|
2117
|
+
bool: True if the partition is licensed and False otherwise
|
|
2186
2118
|
"""
|
|
2187
2119
|
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
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"]:
|
|
2191
2127
|
return False
|
|
2192
2128
|
|
|
2193
|
-
|
|
2194
|
-
accessRoleGroups = accessRolesGetResponse["accessRoleMembers"]["groups"]
|
|
2195
|
-
for accessRoleGroup in accessRoleGroups:
|
|
2196
|
-
if accessRoleGroup["name"] == group:
|
|
2197
|
-
logger.debug(
|
|
2198
|
-
"Group -> %s already added to access role -> %s", group, access_role
|
|
2199
|
-
)
|
|
2200
|
-
return True
|
|
2129
|
+
partitions = response["listUserPartitionResult"]["_userPartitions"]
|
|
2201
2130
|
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2131
|
+
if not partitions:
|
|
2132
|
+
return False
|
|
2133
|
+
|
|
2134
|
+
partition = next(
|
|
2135
|
+
(item for item in partitions if item["name"] == partition_name),
|
|
2136
|
+
None,
|
|
2206
2137
|
)
|
|
2207
2138
|
|
|
2208
|
-
|
|
2209
|
-
|
|
2139
|
+
if partition:
|
|
2140
|
+
return True
|
|
2210
2141
|
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2142
|
+
return False
|
|
2143
|
+
|
|
2144
|
+
# end method definition
|
|
2145
|
+
|
|
2146
|
+
def import_synchronized_partition_members(self, name: str) -> dict:
|
|
2147
|
+
"""Import users and groups to partition
|
|
2214
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'
|
|
2215
2156
|
logger.debug(
|
|
2216
|
-
"
|
|
2217
|
-
|
|
2218
|
-
access_role,
|
|
2157
|
+
"Importing users and groups in to partition -> %s; calling -> %s",
|
|
2158
|
+
name,
|
|
2219
2159
|
request_url,
|
|
2220
2160
|
)
|
|
2221
|
-
|
|
2222
2161
|
retries = 0
|
|
2223
2162
|
while True:
|
|
2224
2163
|
response = requests.post(
|
|
2225
2164
|
url=request_url,
|
|
2226
|
-
json=
|
|
2165
|
+
json=command,
|
|
2227
2166
|
headers=REQUEST_HEADERS,
|
|
2228
2167
|
cookies=self.cookie(),
|
|
2229
2168
|
timeout=None,
|
|
2230
2169
|
)
|
|
2231
|
-
if response.
|
|
2170
|
+
if response.status_code == 204:
|
|
2232
2171
|
return True
|
|
2172
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
2233
2173
|
elif response.status_code == 401 and retries == 0:
|
|
2234
2174
|
logger.debug("Session has expired - try to re-authenticate...")
|
|
2235
2175
|
self.authenticate(revalidate=True)
|
|
2236
2176
|
retries += 1
|
|
2237
2177
|
else:
|
|
2238
2178
|
logger.error(
|
|
2239
|
-
"Failed to
|
|
2240
|
-
|
|
2241
|
-
access_role,
|
|
2179
|
+
"Failed to Import users and groups to synchronized partition -> %s; error -> %s (%s)",
|
|
2180
|
+
name,
|
|
2242
2181
|
response.text,
|
|
2243
2182
|
response.status_code,
|
|
2244
2183
|
)
|
|
2245
|
-
return
|
|
2246
|
-
|
|
2247
|
-
# end method definition
|
|
2248
|
-
|
|
2249
|
-
def
|
|
2250
|
-
|
|
2251
|
-
) -> dict | None:
|
|
2252
|
-
"""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
|
|
2253
2190
|
|
|
2254
2191
|
Args:
|
|
2255
|
-
name (str): name of the
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
[{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
|
|
2259
2195
|
Returns:
|
|
2260
|
-
dict: Request response
|
|
2196
|
+
dict: Request response or None if the creation fails.
|
|
2261
2197
|
"""
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
request_url =
|
|
2276
|
-
|
|
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()
|
|
2277
2212
|
logger.debug(
|
|
2278
|
-
"
|
|
2213
|
+
"Adding synchronized partition -> %s (%s); calling -> %s",
|
|
2279
2214
|
name,
|
|
2280
|
-
|
|
2215
|
+
description,
|
|
2281
2216
|
request_url,
|
|
2282
2217
|
)
|
|
2283
|
-
|
|
2218
|
+
synchronizedPartitionPostBodyJson["ipAuthentication"]["bindPassword"] = self.config()["bindPassword"]
|
|
2284
2219
|
retries = 0
|
|
2285
2220
|
while True:
|
|
2286
|
-
response = requests.
|
|
2221
|
+
response = requests.post(
|
|
2287
2222
|
url=request_url,
|
|
2288
|
-
json=
|
|
2223
|
+
json=synchronizedPartitionPostBodyJson,
|
|
2289
2224
|
headers=REQUEST_HEADERS,
|
|
2290
2225
|
cookies=self.cookie(),
|
|
2291
2226
|
timeout=None,
|
|
@@ -2299,14 +2234,14 @@ class OTDS:
|
|
|
2299
2234
|
retries += 1
|
|
2300
2235
|
else:
|
|
2301
2236
|
logger.error(
|
|
2302
|
-
"Failed to
|
|
2237
|
+
"Failed to add synchronized partition -> %s; error -> %s (%s)",
|
|
2303
2238
|
name,
|
|
2304
2239
|
response.text,
|
|
2305
2240
|
response.status_code,
|
|
2306
2241
|
)
|
|
2307
2242
|
return None
|
|
2308
|
-
|
|
2309
|
-
# end method definition
|
|
2243
|
+
|
|
2244
|
+
# end of method definition
|
|
2310
2245
|
|
|
2311
2246
|
def add_system_attribute(
|
|
2312
2247
|
self, name: str, value: str, description: str = ""
|
|
@@ -2321,7 +2256,7 @@ class OTDS:
|
|
|
2321
2256
|
dict: Request response (dictionary) or None if the REST call fails.
|
|
2322
2257
|
"""
|
|
2323
2258
|
|
|
2324
|
-
|
|
2259
|
+
system_attribute_post_body_json = {
|
|
2325
2260
|
"name": name,
|
|
2326
2261
|
"value": value,
|
|
2327
2262
|
"friendlyName": description,
|
|
@@ -2331,7 +2266,7 @@ class OTDS:
|
|
|
2331
2266
|
|
|
2332
2267
|
if description:
|
|
2333
2268
|
logger.debug(
|
|
2334
|
-
"Add system attribute -> %s (%s) with value -> %s; calling -> %s",
|
|
2269
|
+
"Add system attribute -> '%s' ('%s') with value -> %s; calling -> %s",
|
|
2335
2270
|
name,
|
|
2336
2271
|
description,
|
|
2337
2272
|
value,
|
|
@@ -2339,37 +2274,21 @@ class OTDS:
|
|
|
2339
2274
|
)
|
|
2340
2275
|
else:
|
|
2341
2276
|
logger.debug(
|
|
2342
|
-
"Add system attribute -> %s with value -> %s; calling -> %s",
|
|
2277
|
+
"Add system attribute -> '%s' with value -> %s; calling -> %s",
|
|
2343
2278
|
name,
|
|
2344
2279
|
value,
|
|
2345
2280
|
request_url,
|
|
2346
2281
|
)
|
|
2347
2282
|
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
if response.ok:
|
|
2358
|
-
return self.parse_request_response(response)
|
|
2359
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2360
|
-
elif response.status_code == 401 and retries == 0:
|
|
2361
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2362
|
-
self.authenticate(revalidate=True)
|
|
2363
|
-
retries += 1
|
|
2364
|
-
else:
|
|
2365
|
-
logger.error(
|
|
2366
|
-
"Failed to add system attribute -> %s with value -> %s; error -> %s (%s)",
|
|
2367
|
-
name,
|
|
2368
|
-
value,
|
|
2369
|
-
response.text,
|
|
2370
|
-
response.status_code,
|
|
2371
|
-
)
|
|
2372
|
-
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
|
+
)
|
|
2373
2292
|
|
|
2374
2293
|
# end method definition
|
|
2375
2294
|
|
|
@@ -2384,30 +2303,14 @@ class OTDS:
|
|
|
2384
2303
|
|
|
2385
2304
|
request_url = "{}/whitelist".format(self.config()["systemConfigUrl"])
|
|
2386
2305
|
|
|
2387
|
-
logger.debug("
|
|
2306
|
+
logger.debug("Get trusted sites; calling -> %s", request_url)
|
|
2388
2307
|
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
timeout=None,
|
|
2396
|
-
)
|
|
2397
|
-
if response.ok:
|
|
2398
|
-
return self.parse_request_response(response)
|
|
2399
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2400
|
-
elif response.status_code == 401 and retries == 0:
|
|
2401
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2402
|
-
self.authenticate(revalidate=True)
|
|
2403
|
-
retries += 1
|
|
2404
|
-
else:
|
|
2405
|
-
logger.error(
|
|
2406
|
-
"Failed to retrieve trusted sites; error -> %s (%s)",
|
|
2407
|
-
response.text,
|
|
2408
|
-
response.status_code,
|
|
2409
|
-
)
|
|
2410
|
-
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
|
+
)
|
|
2411
2314
|
|
|
2412
2315
|
# end method definition
|
|
2413
2316
|
|
|
@@ -2420,38 +2323,36 @@ class OTDS:
|
|
|
2420
2323
|
dict: Request response or None if the REST call fails.
|
|
2421
2324
|
"""
|
|
2422
2325
|
|
|
2423
|
-
|
|
2326
|
+
trusted_site_post_body_json = {"stringList": [trusted_site]}
|
|
2424
2327
|
|
|
2425
2328
|
# we need to first retrieve the existing sites and then
|
|
2426
2329
|
# append the new one:
|
|
2427
|
-
|
|
2330
|
+
existing_trusted_sites = self.get_trusted_sites()
|
|
2428
2331
|
|
|
2429
|
-
if
|
|
2430
|
-
|
|
2431
|
-
|
|
2332
|
+
if existing_trusted_sites:
|
|
2333
|
+
trusted_site_post_body_json["stringList"].extend(
|
|
2334
|
+
existing_trusted_sites["stringList"]
|
|
2432
2335
|
)
|
|
2433
2336
|
|
|
2434
2337
|
request_url = "{}/whitelist".format(self.config()["systemConfigUrl"])
|
|
2435
2338
|
|
|
2436
|
-
logger.debug(
|
|
2339
|
+
logger.debug(
|
|
2340
|
+
"Add trusted site -> '%s'; calling -> %s", trusted_site, request_url
|
|
2341
|
+
)
|
|
2437
2342
|
|
|
2438
|
-
response =
|
|
2343
|
+
response = self.do_request(
|
|
2439
2344
|
url=request_url,
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
cookies=self.cookie(),
|
|
2345
|
+
method="PUT",
|
|
2346
|
+
json_data=trusted_site_post_body_json,
|
|
2443
2347
|
timeout=None,
|
|
2348
|
+
failure_message="Failed to add trusted site -> '{}'".format(trusted_site),
|
|
2349
|
+
parse_request_response=False, # don't parse it!
|
|
2444
2350
|
)
|
|
2351
|
+
|
|
2445
2352
|
if not response.ok:
|
|
2446
|
-
logger.error(
|
|
2447
|
-
"Failed to add trusted site -> %s; error -> %s (%s)",
|
|
2448
|
-
trusted_site,
|
|
2449
|
-
response.text,
|
|
2450
|
-
response.status_code,
|
|
2451
|
-
)
|
|
2452
2353
|
return None
|
|
2453
2354
|
|
|
2454
|
-
return response
|
|
2355
|
+
return response
|
|
2455
2356
|
|
|
2456
2357
|
# end method definition
|
|
2457
2358
|
|
|
@@ -2464,7 +2365,7 @@ class OTDS:
|
|
|
2464
2365
|
Request response (json) or None if the REST call fails.
|
|
2465
2366
|
"""
|
|
2466
2367
|
|
|
2467
|
-
|
|
2368
|
+
audit_put_body_json = {
|
|
2468
2369
|
"daysToKeep": "7",
|
|
2469
2370
|
"enabled": "true",
|
|
2470
2371
|
"auditTo": "DATABASE",
|
|
@@ -2523,20 +2424,14 @@ class OTDS:
|
|
|
2523
2424
|
|
|
2524
2425
|
logger.debug("Enable audit; calling -> %s", request_url)
|
|
2525
2426
|
|
|
2526
|
-
|
|
2427
|
+
return self.do_request(
|
|
2527
2428
|
url=request_url,
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
cookies=self.cookie(),
|
|
2429
|
+
method="PUT",
|
|
2430
|
+
json_data=audit_put_body_json,
|
|
2531
2431
|
timeout=None,
|
|
2432
|
+
failure_message="Failed to enable audit",
|
|
2433
|
+
parse_request_response=False,
|
|
2532
2434
|
)
|
|
2533
|
-
if not response.ok:
|
|
2534
|
-
logger.error(
|
|
2535
|
-
"Failed to enable audit; error -> %s (%s)",
|
|
2536
|
-
response.text,
|
|
2537
|
-
response.status_code,
|
|
2538
|
-
)
|
|
2539
|
-
return response
|
|
2540
2435
|
|
|
2541
2436
|
# end method definition
|
|
2542
2437
|
|
|
@@ -2620,7 +2515,7 @@ class OTDS:
|
|
|
2620
2515
|
if default_scopes is None:
|
|
2621
2516
|
default_scopes = []
|
|
2622
2517
|
|
|
2623
|
-
|
|
2518
|
+
oauth_client_post_body_json = {
|
|
2624
2519
|
"id": client_id,
|
|
2625
2520
|
"description": description,
|
|
2626
2521
|
"redirectURLs": redirect_urls,
|
|
@@ -2638,41 +2533,24 @@ class OTDS:
|
|
|
2638
2533
|
|
|
2639
2534
|
# Do we have a predefined client secret?
|
|
2640
2535
|
if secret:
|
|
2641
|
-
|
|
2536
|
+
oauth_client_post_body_json["secret"] = secret
|
|
2642
2537
|
|
|
2643
2538
|
request_url = self.oauth_client_url()
|
|
2644
2539
|
|
|
2645
2540
|
logger.debug(
|
|
2646
|
-
"Adding oauth client -> %s (%s); calling -> %s",
|
|
2541
|
+
"Adding oauth client -> '%s' (%s); calling -> %s",
|
|
2647
2542
|
description,
|
|
2648
2543
|
client_id,
|
|
2649
2544
|
request_url,
|
|
2650
2545
|
)
|
|
2651
2546
|
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
timeout=None,
|
|
2660
|
-
)
|
|
2661
|
-
if response.ok:
|
|
2662
|
-
return self.parse_request_response(response)
|
|
2663
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2664
|
-
elif response.status_code == 401 and retries == 0:
|
|
2665
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2666
|
-
self.authenticate(revalidate=True)
|
|
2667
|
-
retries += 1
|
|
2668
|
-
else:
|
|
2669
|
-
logger.error(
|
|
2670
|
-
"Failed to add OAuth client -> %s; error -> %s (%s)",
|
|
2671
|
-
client_id,
|
|
2672
|
-
response.text,
|
|
2673
|
-
response.status_code,
|
|
2674
|
-
)
|
|
2675
|
-
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
|
+
)
|
|
2676
2554
|
|
|
2677
2555
|
# end method definition
|
|
2678
2556
|
|
|
@@ -2688,32 +2566,15 @@ class OTDS:
|
|
|
2688
2566
|
|
|
2689
2567
|
request_url = "{}/{}".format(self.oauth_client_url(), client_id)
|
|
2690
2568
|
|
|
2691
|
-
logger.debug("Get oauth client -> %s; calling -> %s", client_id, request_url)
|
|
2569
|
+
logger.debug("Get oauth client -> '%s'; calling -> %s", client_id, request_url)
|
|
2692
2570
|
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
)
|
|
2701
|
-
if response.ok:
|
|
2702
|
-
return self.parse_request_response(response)
|
|
2703
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2704
|
-
elif response.status_code == 401 and retries == 0:
|
|
2705
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2706
|
-
self.authenticate(revalidate=True)
|
|
2707
|
-
retries += 1
|
|
2708
|
-
else:
|
|
2709
|
-
if show_error:
|
|
2710
|
-
logger.error(
|
|
2711
|
-
"Failed to get oauth client -> %s; error -> %s (%s)",
|
|
2712
|
-
client_id,
|
|
2713
|
-
response.text,
|
|
2714
|
-
response.status_code,
|
|
2715
|
-
)
|
|
2716
|
-
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
|
+
)
|
|
2717
2578
|
|
|
2718
2579
|
# end method definition
|
|
2719
2580
|
|
|
@@ -2729,41 +2590,24 @@ class OTDS:
|
|
|
2729
2590
|
dict: Request response (json) or None if the REST call fails.
|
|
2730
2591
|
"""
|
|
2731
2592
|
|
|
2732
|
-
|
|
2593
|
+
oauth_client_patch_body_json = updates
|
|
2733
2594
|
|
|
2734
2595
|
request_url = "{}/{}".format(self.oauth_client_url(), client_id)
|
|
2735
2596
|
|
|
2736
2597
|
logger.debug(
|
|
2737
|
-
"Update OAuth client -> %s with -> %s; calling -> %s",
|
|
2598
|
+
"Update OAuth client -> '%s' with -> %s; calling -> %s",
|
|
2738
2599
|
client_id,
|
|
2739
|
-
updates,
|
|
2600
|
+
str(updates),
|
|
2740
2601
|
request_url,
|
|
2741
2602
|
)
|
|
2742
2603
|
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
timeout=None,
|
|
2751
|
-
)
|
|
2752
|
-
if response.ok:
|
|
2753
|
-
return self.parse_request_response(response)
|
|
2754
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2755
|
-
elif response.status_code == 401 and retries == 0:
|
|
2756
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2757
|
-
self.authenticate(revalidate=True)
|
|
2758
|
-
retries += 1
|
|
2759
|
-
else:
|
|
2760
|
-
logger.error(
|
|
2761
|
-
"Failed to update OAuth client -> %s; error -> %s (%s)",
|
|
2762
|
-
client_id,
|
|
2763
|
-
response.text,
|
|
2764
|
-
response.status_code,
|
|
2765
|
-
)
|
|
2766
|
-
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
|
+
)
|
|
2767
2611
|
|
|
2768
2612
|
# end method definition
|
|
2769
2613
|
|
|
@@ -2779,39 +2623,24 @@ class OTDS:
|
|
|
2779
2623
|
request_url = self.config()["accessRoleUrl"] + "/" + access_role_name
|
|
2780
2624
|
|
|
2781
2625
|
logger.debug(
|
|
2782
|
-
"Get access role -> %s; calling -> %s", access_role_name, request_url
|
|
2626
|
+
"Get access role -> '%s'; calling -> %s", access_role_name, request_url
|
|
2627
|
+
)
|
|
2628
|
+
|
|
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
|
+
),
|
|
2783
2636
|
)
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
while True:
|
|
2787
|
-
response = requests.get(
|
|
2788
|
-
url=request_url,
|
|
2789
|
-
headers=REQUEST_HEADERS,
|
|
2790
|
-
cookies=self.cookie(),
|
|
2791
|
-
timeout=None,
|
|
2792
|
-
)
|
|
2793
|
-
if response.ok:
|
|
2794
|
-
accessRolesJson = self.parse_request_response(response)
|
|
2795
|
-
break
|
|
2796
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2797
|
-
elif response.status_code == 401 and retries == 0:
|
|
2798
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2799
|
-
self.authenticate(revalidate=True)
|
|
2800
|
-
retries += 1
|
|
2801
|
-
else:
|
|
2802
|
-
logger.error(
|
|
2803
|
-
"Failed to retrieve role -> %s; url -> %s : error -> %s (%s)",
|
|
2804
|
-
access_role_name,
|
|
2805
|
-
request_url,
|
|
2806
|
-
response.text,
|
|
2807
|
-
response.status_code,
|
|
2808
|
-
)
|
|
2809
|
-
return None
|
|
2637
|
+
if not access_role:
|
|
2638
|
+
return None
|
|
2810
2639
|
|
|
2811
2640
|
# Checking if OAuthClients partition already added to access role
|
|
2812
|
-
|
|
2813
|
-
for
|
|
2814
|
-
if
|
|
2641
|
+
user_partitions = access_role["accessRoleMembers"]["userPartitions"]
|
|
2642
|
+
for user_partition in user_partitions:
|
|
2643
|
+
if user_partition["userPartition"] == "OAuthClients":
|
|
2815
2644
|
logger.error(
|
|
2816
2645
|
"OAuthClients partition already added to role -> %s",
|
|
2817
2646
|
access_role_name,
|
|
@@ -2821,58 +2650,42 @@ class OTDS:
|
|
|
2821
2650
|
# Getting location info for the OAuthClients partition
|
|
2822
2651
|
# so it can be added to access roles json
|
|
2823
2652
|
request_url = self.config()["partitionsUrl"] + "/OAuthClients"
|
|
2824
|
-
|
|
2653
|
+
|
|
2654
|
+
response = self.do_request(
|
|
2825
2655
|
url=request_url,
|
|
2826
|
-
|
|
2827
|
-
cookies=self.cookie(),
|
|
2656
|
+
method="GET",
|
|
2828
2657
|
timeout=None,
|
|
2658
|
+
failure_message="Failed to get partition info for OAuthClients for role -> '{}'".format(
|
|
2659
|
+
access_role_name
|
|
2660
|
+
),
|
|
2829
2661
|
)
|
|
2830
|
-
if
|
|
2831
|
-
response_dict = self.parse_request_response(partitionsResponse)
|
|
2832
|
-
if not response_dict:
|
|
2833
|
-
return None
|
|
2834
|
-
oauthClientLocation = response_dict["location"]
|
|
2835
|
-
else:
|
|
2836
|
-
logger.error(
|
|
2837
|
-
"Failed to get partition info for OAuthClients; url -> %s : error -> %s (%s)",
|
|
2838
|
-
request_url,
|
|
2839
|
-
partitionsResponse.text,
|
|
2840
|
-
response.status_code,
|
|
2841
|
-
)
|
|
2662
|
+
if not response:
|
|
2842
2663
|
return None
|
|
2843
2664
|
|
|
2665
|
+
oauth_client_location = response["location"]
|
|
2666
|
+
|
|
2844
2667
|
# adding OAuthClients info to acess roles organizational units
|
|
2845
|
-
|
|
2846
|
-
"location":
|
|
2847
|
-
"name":
|
|
2668
|
+
oauth_clients_ou_block = {
|
|
2669
|
+
"location": oauth_client_location,
|
|
2670
|
+
"name": oauth_client_location,
|
|
2848
2671
|
"userPartition": None,
|
|
2849
2672
|
}
|
|
2850
|
-
|
|
2851
|
-
|
|
2673
|
+
access_role["accessRoleMembers"]["organizationalUnits"].append(
|
|
2674
|
+
oauth_clients_ou_block
|
|
2852
2675
|
)
|
|
2853
2676
|
|
|
2854
|
-
|
|
2677
|
+
return self.do_request(
|
|
2855
2678
|
url=request_url,
|
|
2856
|
-
|
|
2857
|
-
headers=REQUEST_HEADERS,
|
|
2858
|
-
cookies=self.cookie(),
|
|
2679
|
+
method="PUT",
|
|
2859
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,
|
|
2860
2687
|
)
|
|
2861
2688
|
|
|
2862
|
-
if response.ok:
|
|
2863
|
-
logger.debug(
|
|
2864
|
-
"OauthClients partition successfully added to access role -> %s",
|
|
2865
|
-
access_role_name,
|
|
2866
|
-
)
|
|
2867
|
-
else:
|
|
2868
|
-
logger.warning(
|
|
2869
|
-
"Status code of -> %s returned attempting to add OAuthClients to access role -> %s: error -> %s",
|
|
2870
|
-
response.status_code,
|
|
2871
|
-
access_role_name,
|
|
2872
|
-
response.text,
|
|
2873
|
-
)
|
|
2874
|
-
return response
|
|
2875
|
-
|
|
2876
2689
|
# end method definition
|
|
2877
2690
|
|
|
2878
2691
|
def get_access_token(self, client_id: str, client_secret: str) -> str | None:
|
|
@@ -2963,33 +2776,16 @@ class OTDS:
|
|
|
2963
2776
|
request_url = "{}/{}".format(self.auth_handler_url(), name)
|
|
2964
2777
|
|
|
2965
2778
|
logger.debug(
|
|
2966
|
-
"Getting authentication handler -> %s; calling -> %s", name, request_url
|
|
2779
|
+
"Getting authentication handler -> '%s'; calling -> %s", name, request_url
|
|
2967
2780
|
)
|
|
2968
2781
|
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
)
|
|
2977
|
-
if response.ok:
|
|
2978
|
-
return self.parse_request_response(response)
|
|
2979
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2980
|
-
elif response.status_code == 401 and retries == 0:
|
|
2981
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2982
|
-
self.authenticate(revalidate=True)
|
|
2983
|
-
retries += 1
|
|
2984
|
-
else:
|
|
2985
|
-
if show_error:
|
|
2986
|
-
logger.error(
|
|
2987
|
-
"Failed to get authentication handler -> %s; warning -> %s (%s)",
|
|
2988
|
-
name,
|
|
2989
|
-
response.text,
|
|
2990
|
-
response.status_code,
|
|
2991
|
-
)
|
|
2992
|
-
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
|
+
)
|
|
2993
2789
|
|
|
2994
2790
|
# end method definition
|
|
2995
2791
|
|
|
@@ -3032,7 +2828,7 @@ class OTDS:
|
|
|
3032
2828
|
if auth_principal_attributes is None:
|
|
3033
2829
|
auth_principal_attributes = ["oTExternalID1", "oTUserID1"]
|
|
3034
2830
|
|
|
3035
|
-
|
|
2831
|
+
auth_handler_post_body_json = {
|
|
3036
2832
|
"_name": name,
|
|
3037
2833
|
"_description": description,
|
|
3038
2834
|
"_class": "com.opentext.otds.as.drivers.saml.SAML2Handler",
|
|
@@ -3315,36 +3111,19 @@ class OTDS:
|
|
|
3315
3111
|
request_url = self.auth_handler_url()
|
|
3316
3112
|
|
|
3317
3113
|
logger.debug(
|
|
3318
|
-
"Adding SAML auth handler -> %s (%s); calling -> %s",
|
|
3114
|
+
"Adding SAML auth handler -> '%s' ('%s'); calling -> %s",
|
|
3319
3115
|
name,
|
|
3320
3116
|
description,
|
|
3321
3117
|
request_url,
|
|
3322
3118
|
)
|
|
3323
3119
|
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
timeout=None,
|
|
3332
|
-
)
|
|
3333
|
-
if response.ok:
|
|
3334
|
-
return self.parse_request_response(response)
|
|
3335
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
3336
|
-
elif response.status_code == 401 and retries == 0:
|
|
3337
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
3338
|
-
self.authenticate(revalidate=True)
|
|
3339
|
-
retries += 1
|
|
3340
|
-
else:
|
|
3341
|
-
logger.error(
|
|
3342
|
-
"Failed to add SAML auth handler -> %s; error -> %s (%s)",
|
|
3343
|
-
name,
|
|
3344
|
-
response.text,
|
|
3345
|
-
response.status_code,
|
|
3346
|
-
)
|
|
3347
|
-
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
|
+
)
|
|
3348
3127
|
|
|
3349
3128
|
# end method definition
|
|
3350
3129
|
|
|
@@ -3379,7 +3158,7 @@ class OTDS:
|
|
|
3379
3158
|
auth_principal_attributes = ["oTExternalID1"]
|
|
3380
3159
|
|
|
3381
3160
|
# 1. Prepare the body for the AuthHandler REST call:
|
|
3382
|
-
|
|
3161
|
+
auth_handler_post_body_json = {
|
|
3383
3162
|
"_name": name,
|
|
3384
3163
|
"_description": description,
|
|
3385
3164
|
"_class": "com.opentext.otds.as.drivers.sapssoext.SAPSSOEXTAuthHandler",
|
|
@@ -3436,42 +3215,39 @@ class OTDS:
|
|
|
3436
3215
|
request_url = self.auth_handler_url()
|
|
3437
3216
|
|
|
3438
3217
|
logger.debug(
|
|
3439
|
-
"Adding SAP auth handler -> %s (%s); calling -> %s",
|
|
3218
|
+
"Adding SAP auth handler -> '%s' ('%s'); calling -> %s",
|
|
3440
3219
|
name,
|
|
3441
3220
|
description,
|
|
3442
3221
|
request_url,
|
|
3443
3222
|
)
|
|
3444
3223
|
|
|
3445
|
-
response =
|
|
3224
|
+
response = self.do_request(
|
|
3446
3225
|
url=request_url,
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
cookies=self.cookie(),
|
|
3226
|
+
method="POST",
|
|
3227
|
+
json_data=auth_handler_post_body_json,
|
|
3450
3228
|
timeout=None,
|
|
3229
|
+
failure_message="Failed to add SAP auth handler -> '{}'".format(name),
|
|
3230
|
+
parse_request_response=False,
|
|
3451
3231
|
)
|
|
3452
|
-
if not response.ok:
|
|
3453
|
-
logger.error(
|
|
3454
|
-
"Failed to add SAP auth handler -> %s; error -> %s (%s)",
|
|
3455
|
-
name,
|
|
3456
|
-
response.text,
|
|
3457
|
-
response.status_code,
|
|
3458
|
-
)
|
|
3232
|
+
if not response or not response.ok:
|
|
3459
3233
|
return None
|
|
3460
3234
|
|
|
3461
3235
|
# 3. Upload the certificate file:
|
|
3462
3236
|
|
|
3463
3237
|
# Check that the certificate (PSE) file is readable:
|
|
3464
|
-
logger.debug("Reading certificate file -> %s...", certificate_file)
|
|
3238
|
+
logger.debug("Reading certificate file -> '%s'...", certificate_file)
|
|
3465
3239
|
try:
|
|
3466
3240
|
# PSE files are binary - so we need to open with "rb":
|
|
3467
|
-
with open(certificate_file, "rb") as
|
|
3468
|
-
|
|
3469
|
-
if not
|
|
3470
|
-
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
|
+
)
|
|
3471
3247
|
return None
|
|
3472
3248
|
except IOError as exception:
|
|
3473
3249
|
logger.error(
|
|
3474
|
-
"Unable to open certificate file -> %s; error -> %s",
|
|
3250
|
+
"Unable to open certificate file -> '%s'; error -> %s",
|
|
3475
3251
|
certificate_file,
|
|
3476
3252
|
exception.strerror,
|
|
3477
3253
|
)
|
|
@@ -3482,18 +3258,20 @@ class OTDS:
|
|
|
3482
3258
|
try:
|
|
3483
3259
|
# If file is not base64 encoded the next statement will throw an exception
|
|
3484
3260
|
# (this is good)
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
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"):
|
|
3488
3266
|
logger.debug(
|
|
3489
|
-
"Certificate file -> %s is base64 encoded", certificate_file
|
|
3267
|
+
"Certificate file -> '%s' is base64 encoded", certificate_file
|
|
3490
3268
|
)
|
|
3491
3269
|
cert_file_encoded = True
|
|
3492
3270
|
else:
|
|
3493
3271
|
cert_file_encoded = False
|
|
3494
3272
|
except TypeError:
|
|
3495
3273
|
logger.debug(
|
|
3496
|
-
"Certificate file -> %s is not base64 encoded", certificate_file
|
|
3274
|
+
"Certificate file -> '%s' is not base64 encoded", certificate_file
|
|
3497
3275
|
)
|
|
3498
3276
|
cert_file_encoded = False
|
|
3499
3277
|
|
|
@@ -3502,23 +3280,23 @@ class OTDS:
|
|
|
3502
3280
|
logger.debug("Writing decoded certificate file -> %s...", certificate_file)
|
|
3503
3281
|
try:
|
|
3504
3282
|
# PSE files need to be binary - so we need to open with "wb":
|
|
3505
|
-
with open(certificate_file, "wb") as
|
|
3506
|
-
|
|
3283
|
+
with open(certificate_file, "wb") as cert_file:
|
|
3284
|
+
cert_file.write(base64.b64decode(cert_content))
|
|
3507
3285
|
except IOError as exception:
|
|
3508
3286
|
logger.error(
|
|
3509
|
-
"Failed writing to file -> %s; error -> %s",
|
|
3287
|
+
"Failed writing to file -> '%s'; error -> %s",
|
|
3510
3288
|
certificate_file,
|
|
3511
3289
|
exception.strerror,
|
|
3512
3290
|
)
|
|
3513
3291
|
return None
|
|
3514
3292
|
|
|
3515
|
-
|
|
3293
|
+
auth_handler_post_data = {
|
|
3516
3294
|
"file1_property": "com.opentext.otds.as.drivers.sapssoext.certificate1"
|
|
3517
3295
|
}
|
|
3518
3296
|
|
|
3519
3297
|
# It is important to send the file pointer and not the actual file content
|
|
3520
3298
|
# otherwise the file is send base64 encoded which we don't want:
|
|
3521
|
-
|
|
3299
|
+
auth_handler_post_files = {
|
|
3522
3300
|
"file1": (
|
|
3523
3301
|
os.path.basename(certificate_file),
|
|
3524
3302
|
open(certificate_file, "rb"),
|
|
@@ -3529,7 +3307,7 @@ class OTDS:
|
|
|
3529
3307
|
request_url = self.auth_handler_url() + "/" + name + "/files"
|
|
3530
3308
|
|
|
3531
3309
|
logger.debug(
|
|
3532
|
-
"Uploading certificate file -> %s for SAP auth handler -> %s (%s); calling -> %s",
|
|
3310
|
+
"Uploading certificate file -> '%s' for SAP auth handler -> '%s' ('%s'); calling -> %s",
|
|
3533
3311
|
certificate_file,
|
|
3534
3312
|
name,
|
|
3535
3313
|
description,
|
|
@@ -3541,14 +3319,14 @@ class OTDS:
|
|
|
3541
3319
|
# then requests will send a multipart/form-data POST automatically:
|
|
3542
3320
|
response = requests.post(
|
|
3543
3321
|
url=request_url,
|
|
3544
|
-
data=
|
|
3545
|
-
files=
|
|
3322
|
+
data=auth_handler_post_data,
|
|
3323
|
+
files=auth_handler_post_files,
|
|
3546
3324
|
cookies=self.cookie(),
|
|
3547
3325
|
timeout=None,
|
|
3548
3326
|
)
|
|
3549
3327
|
if not response.ok:
|
|
3550
3328
|
logger.error(
|
|
3551
|
-
"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)",
|
|
3552
3330
|
certificate_file,
|
|
3553
3331
|
name,
|
|
3554
3332
|
response.text,
|
|
@@ -3605,7 +3383,7 @@ class OTDS:
|
|
|
3605
3383
|
auth_principal_attributes = ["oTExtraAttr0"]
|
|
3606
3384
|
|
|
3607
3385
|
# 1. Prepare the body for the AuthHandler REST call:
|
|
3608
|
-
|
|
3386
|
+
auth_handler_post_body_json = {
|
|
3609
3387
|
"_name": name,
|
|
3610
3388
|
"_description": description,
|
|
3611
3389
|
"_class": "com.opentext.otds.as.drivers.http.OAuth2Handler",
|
|
@@ -3945,36 +3723,19 @@ class OTDS:
|
|
|
3945
3723
|
request_url = self.auth_handler_url()
|
|
3946
3724
|
|
|
3947
3725
|
logger.debug(
|
|
3948
|
-
"Adding OAuth auth handler -> %s (%s); calling -> %s",
|
|
3726
|
+
"Adding OAuth auth handler -> '%s' ('%s'); calling -> %s",
|
|
3949
3727
|
name,
|
|
3950
3728
|
description,
|
|
3951
3729
|
request_url,
|
|
3952
3730
|
)
|
|
3953
3731
|
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
timeout=None,
|
|
3962
|
-
)
|
|
3963
|
-
if response.ok:
|
|
3964
|
-
return self.parse_request_response(response)
|
|
3965
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
3966
|
-
elif response.status_code == 401 and retries == 0:
|
|
3967
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
3968
|
-
self.authenticate(revalidate=True)
|
|
3969
|
-
retries += 1
|
|
3970
|
-
else:
|
|
3971
|
-
logger.error(
|
|
3972
|
-
"Failed to add OAuth auth handler -> %s; error -> %s (%s)",
|
|
3973
|
-
name,
|
|
3974
|
-
response.text,
|
|
3975
|
-
response.status_code,
|
|
3976
|
-
)
|
|
3977
|
-
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
|
+
)
|
|
3978
3739
|
|
|
3979
3740
|
# end method definition
|
|
3980
3741
|
|
|
@@ -3989,7 +3750,9 @@ class OTDS:
|
|
|
3989
3750
|
|
|
3990
3751
|
resource = self.get_resource(resource_name)
|
|
3991
3752
|
if not resource:
|
|
3992
|
-
logger.error(
|
|
3753
|
+
logger.error(
|
|
3754
|
+
"Resource -> '%s' not found - cannot consolidate", resource_name
|
|
3755
|
+
)
|
|
3993
3756
|
return False
|
|
3994
3757
|
|
|
3995
3758
|
resource_dn = resource["resourceDN"]
|
|
@@ -3997,7 +3760,7 @@ class OTDS:
|
|
|
3997
3760
|
logger.error("Resource DN is empty - cannot consolidate")
|
|
3998
3761
|
return False
|
|
3999
3762
|
|
|
4000
|
-
|
|
3763
|
+
consolidation_post_body_json = {
|
|
4001
3764
|
"cleanupUsersInResource": False,
|
|
4002
3765
|
"cleanupGroupsInResource": False,
|
|
4003
3766
|
"resourceList": [resource_dn],
|
|
@@ -4007,32 +3770,27 @@ class OTDS:
|
|
|
4007
3770
|
request_url = "{}".format(self.consolidation_url())
|
|
4008
3771
|
|
|
4009
3772
|
logger.debug(
|
|
4010
|
-
"Consolidation of resource -> %s; calling -> %s",
|
|
3773
|
+
"Consolidation of resource -> %s (%s); calling -> %s",
|
|
3774
|
+
resource_name,
|
|
3775
|
+
resource_dn,
|
|
3776
|
+
request_url,
|
|
4011
3777
|
)
|
|
4012
3778
|
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
retries += 1
|
|
4029
|
-
else:
|
|
4030
|
-
logger.error(
|
|
4031
|
-
"Failed to consolidate; error -> %s (%s)",
|
|
4032
|
-
response.text,
|
|
4033
|
-
response.status_code,
|
|
4034
|
-
)
|
|
4035
|
-
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
|
|
4036
3794
|
|
|
4037
3795
|
# end method definition
|
|
4038
3796
|
|
|
@@ -4057,7 +3815,7 @@ class OTDS:
|
|
|
4057
3815
|
if impersonation_list is None:
|
|
4058
3816
|
impersonation_list = []
|
|
4059
3817
|
|
|
4060
|
-
|
|
3818
|
+
impersonation_put_body_json = {
|
|
4061
3819
|
"allowImpersonation": allow_impersonation,
|
|
4062
3820
|
"impersonateList": impersonation_list,
|
|
4063
3821
|
}
|
|
@@ -4065,34 +3823,26 @@ class OTDS:
|
|
|
4065
3823
|
request_url = "{}/{}/impersonation".format(self.resource_url(), resource_name)
|
|
4066
3824
|
|
|
4067
3825
|
logger.debug(
|
|
4068
|
-
"Impersonation settings for resource -> %s; calling -> %s",
|
|
3826
|
+
"Impersonation settings for resource -> '%s'; calling -> %s",
|
|
4069
3827
|
resource_name,
|
|
4070
3828
|
request_url,
|
|
4071
3829
|
)
|
|
4072
3830
|
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
retries += 1
|
|
4089
|
-
else:
|
|
4090
|
-
logger.error(
|
|
4091
|
-
"Failed to set impersonation for resource -> %s; error -> %s",
|
|
4092
|
-
resource_name,
|
|
4093
|
-
response.text,
|
|
4094
|
-
)
|
|
4095
|
-
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
|
|
4096
3846
|
|
|
4097
3847
|
# end method definition
|
|
4098
3848
|
|
|
@@ -4116,7 +3866,7 @@ class OTDS:
|
|
|
4116
3866
|
if impersonation_list is None:
|
|
4117
3867
|
impersonation_list = []
|
|
4118
3868
|
|
|
4119
|
-
|
|
3869
|
+
impersonation_put_body_json = {
|
|
4120
3870
|
"allowImpersonation": allow_impersonation,
|
|
4121
3871
|
"impersonateList": impersonation_list,
|
|
4122
3872
|
}
|
|
@@ -4124,35 +3874,26 @@ class OTDS:
|
|
|
4124
3874
|
request_url = "{}/{}/impersonation".format(self.oauth_client_url(), client_id)
|
|
4125
3875
|
|
|
4126
3876
|
logger.debug(
|
|
4127
|
-
"Impersonation settings for OAuth Client -> %s; calling -> %s",
|
|
3877
|
+
"Impersonation settings for OAuth Client -> '%s'; calling -> %s",
|
|
4128
3878
|
client_id,
|
|
4129
3879
|
request_url,
|
|
4130
3880
|
)
|
|
4131
3881
|
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
retries += 1
|
|
4148
|
-
else:
|
|
4149
|
-
logger.error(
|
|
4150
|
-
"Failed to set impersonation for OAuth Client -> %s; error -> %s (%s)",
|
|
4151
|
-
client_id,
|
|
4152
|
-
response.text,
|
|
4153
|
-
response.status_code,
|
|
4154
|
-
)
|
|
4155
|
-
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
|
|
4156
3897
|
|
|
4157
3898
|
# end method definition
|
|
4158
3899
|
|
|
@@ -4189,28 +3930,12 @@ class OTDS:
|
|
|
4189
3930
|
|
|
4190
3931
|
logger.debug("Getting password policy; calling -> %s", request_url)
|
|
4191
3932
|
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
timeout=None,
|
|
4199
|
-
)
|
|
4200
|
-
if response.ok:
|
|
4201
|
-
return self.parse_request_response(response)
|
|
4202
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
4203
|
-
elif response.status_code == 401 and retries == 0:
|
|
4204
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
4205
|
-
self.authenticate(revalidate=True)
|
|
4206
|
-
retries += 1
|
|
4207
|
-
else:
|
|
4208
|
-
logger.error(
|
|
4209
|
-
"Failed to get password policy; error -> %s (%s)",
|
|
4210
|
-
response.text,
|
|
4211
|
-
response.status_code,
|
|
4212
|
-
)
|
|
4213
|
-
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
|
+
)
|
|
4214
3939
|
|
|
4215
3940
|
# end method definition
|
|
4216
3941
|
|
|
@@ -4250,33 +3975,24 @@ class OTDS:
|
|
|
4250
3975
|
|
|
4251
3976
|
logger.debug(
|
|
4252
3977
|
"Update password policy with these new values -> %s; calling -> %s",
|
|
4253
|
-
update_values,
|
|
3978
|
+
str(update_values),
|
|
4254
3979
|
request_url,
|
|
4255
3980
|
)
|
|
4256
3981
|
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
retries += 1
|
|
4273
|
-
else:
|
|
4274
|
-
logger.error(
|
|
4275
|
-
"Failed to update password policy with values -> %s; error -> %s (%s)",
|
|
4276
|
-
update_values,
|
|
4277
|
-
response.text,
|
|
4278
|
-
response.status_code,
|
|
4279
|
-
)
|
|
4280
|
-
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
|
|
4281
3997
|
|
|
4282
3998
|
# end method definition
|