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/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
- authenticate : authenticates at OTDS server
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
- add_license_to_resource : Add (or update) a product license to OTDS
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 add_license_to_resource(
480
- self,
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
- path_to_license_file (str): fully qualified filename of the license file
491
- product_name (str): product name
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 (dictionary) or None if the REST call fails
674
+ dict: Request response or None if the creation fails.
497
675
  """
498
676
 
499
- logger.debug("Reading license file -> %s...", path_to_license_file)
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.license_url()
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 product license -> %s for product -> %s to resource -> %s; calling -> %s",
537
- path_to_license_file,
538
- product_description,
539
- resource_id,
682
+ "Adding user partition -> '%s' (%s); calling -> %s",
683
+ name,
684
+ description,
540
685
  request_url,
541
686
  )
542
687
 
543
- retries = 0
544
- while True:
545
- if update:
546
- # Do a REST PUT call for update an existing license:
547
- response = requests.put(
548
- url=request_url,
549
- json=licensePostBodyJson,
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 get_license_for_resource(self, resource_id: str):
583
- """Get a product license for a resource in OTDS.
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
- resource_id (str): OTDS resource ID (this is ID not the resource name!)
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
- Licenses for a resource or None if the REST call fails
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
- retries = 0
616
- while True:
617
- response = requests.get(
618
- url=request_url,
619
- headers=REQUEST_HEADERS,
620
- cookies=self.cookie(),
621
- timeout=None,
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 delete_license_from_resource(self, resource_id: str, license_id: str) -> bool:
645
- """Delete a product license for a resource in OTDS.
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
- resource_id (str): OTDS resource ID (this is ID not the resource name!)
649
- license_id (str): OTDS license ID (this is the ID not the license name!)
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
- bool: True if successful or False if the REST call fails
742
+ dict: Request response or None if the creation fails.
652
743
  """
653
744
 
654
- request_url = "{}/{}".format(self.license_url(), license_id)
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
- "Deleting product license -> %s from resource -> %s; calling -> %s",
658
- license_id,
659
- resource_id,
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
- retries = 0
664
- while True:
665
- response = requests.delete(
666
- url=request_url,
667
- headers=REQUEST_HEADERS,
668
- cookies=self.cookie(),
669
- timeout=None,
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 assign_user_to_license(
691
- self,
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): user partition in OTDS, e.g. "Content Server Members"
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
- bool: True if successful or False if the REST call fails or the license is not found
783
+ dict: Request response or None if the user was not found.
710
784
  """
711
785
 
712
- licenses = self.get_license_for_resource(resource_id)
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
- "Assign license feature -> %s of license -> %s associated with resource -> %s to user -> %s; calling -> %s",
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
- retries = 0
754
- while True:
755
- response = requests.post(
756
- url=request_url,
757
- json=licensePostBodyJson,
758
- headers=REQUEST_HEADERS,
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 assign_partition_to_license(
788
- self,
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
- partition_name (str): user partition in OTDS, e.g. "Content Server Members"
799
- resource_id (str): OTDS resource ID (this is ID not the resource name!)
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
- bool: True if successful or False if the REST call fails or the license is not found
811
+ dict: Request response or None if the user was not found.
805
812
  """
806
813
 
807
- licenses = self.get_license_for_resource(resource_id)
808
- if not licenses:
809
- logger.error(
810
- "Resource with ID -> %s does not exist or has no licenses", resource_id
811
- )
812
- return False
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
- try:
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
- licensePostBodyJson = {
841
- "_oTLicenseType": license_type,
842
- "_oTLicenseProduct": "partitions",
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
- request_url = self.license_url() + "/object/" + license_location
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
- logger.debug(
850
- "Assign license feature -> %s of license -> %s associated with resource -> %s to partition -> %s; calling -> %s",
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 get_licensed_objects(
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
- """Return the licensed objects (users, groups, partitions) in OTDS for a license + license feature
899
- associated with an OTDS resource (like "cs").
854
+ """Update a user attribute with a new value
900
855
 
901
856
  Args:
902
- resource_id (str): OTDS resource ID (this is ID not the resource name!)
903
- license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
904
- license_name (str): name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG"
905
- Returns:
906
- dict: data structure of licensed objects
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
- licenses = self.get_license_for_resource(resource_id)
922
- if not licenses:
923
- logger.error(
924
- "Resource with ID -> %s does not exist or has no licenses", resource_id
925
- )
926
- return False
927
-
928
- # licenses have this format:
929
- # {
930
- # '_oTLicenseType': 'NON-PRODUCTION',
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
- "Get licensed objects for license -> %s and license feature -> %s associated with resource -> %s; calling -> %s",
964
- license_name,
965
- license_feature,
966
- resource_id,
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
- retries = 0
971
- while True:
972
- response = requests.get(
973
- url=request_url,
974
- headers=REQUEST_HEADERS,
975
- cookies=self.cookie(),
976
- timeout=None,
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 is_user_licensed(
999
- self, user_name: str, resource_id: str, license_feature: str, license_name: str
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
- user_name (str): login name of the OTDS user
1005
- resource_id (str): OTDS resource ID (this is ID not the resource name!)
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 if the user is licensed and False otherwise
903
+ bool: True = success, False = error
1011
904
  """
1012
905
 
1013
- response = self.get_licensed_objects(
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
- if not users:
1025
- return False
908
+ logger.debug(
909
+ "Delete user -> '%s' in partition -> '%s'; calling -> %s",
910
+ user_id,
911
+ partition,
912
+ request_url,
913
+ )
1026
914
 
1027
- user = next(
1028
- (item for item in users if item["name"] == user_name),
1029
- None,
915
+ response = self.do_request(
916
+ url=request_url,
917
+ method="DELETE",
918
+ timeout=None,
919
+ failure_message="Failed to delete user -> '{}'".format(user_id),
920
+ parse_request_response=False,
1030
921
  )
1031
922
 
1032
- if user:
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 is_group_licensed(
1040
- self, group_name: str, resource_id: str, license_feature: str, license_name: str
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
- group_name (str): name of the OTDS user group
1046
- resource_id (str): OTDS resource ID (this is ID not the resource name!)
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 if the group is licensed and False otherwise
937
+ bool: True = success, False = error.
1052
938
  """
1053
939
 
1054
- response = self.get_licensed_objects(
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
- groups = response["listGroupsResults"]["groups"]
942
+ request_url = "{}/{}/password".format(self.users_url(), user_id)
1064
943
 
1065
- if not groups:
1066
- return False
944
+ logger.debug(
945
+ "Resetting password for user -> '%s'; calling -> %s", user_id, request_url
946
+ )
1067
947
 
1068
- group = next(
1069
- (item for item in groups if item["name"] == group_name),
1070
- None,
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 group:
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 is_partition_licensed(
1081
- self,
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
- partition_name (str): name of the OTDS user partition, e.g. "Content Server Members"
1091
- resource_id (str): OTDS resource ID (this is ID not the resource name!)
1092
- license_feature (str): name of the license feature, e.g. "X2" or "ADDON_ENGINEERING"
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
- bool: True if the partition is licensed and False otherwise
972
+ dict: Request response (json) or None if the creation fails.
1097
973
  """
1098
974
 
1099
- response = self.get_licensed_objects(
1100
- resource_id=resource_id,
1101
- license_feature=license_feature,
1102
- license_name=license_name,
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
- if not response or not response["listUserPartitionResult"]:
1106
- return False
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
- partitions = response["listUserPartitionResult"]["_userPartitions"]
999
+ # end method definition
1109
1000
 
1110
- if not partitions:
1111
- return False
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
- partition = next(
1114
- (item for item in partitions if item["name"] == partition_name),
1115
- None,
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
- if partition:
1119
- return True
1027
+ request_url = self.groups_url() + "/" + group
1120
1028
 
1121
- return False
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 add_partition(self, name: str, description: str) -> dict | None:
1126
- """Add a new user partition to OTDS
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
- name (str): name of the new partition
1130
- description (str): description of the new partition
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
- dict: Request response or None if the creation fails.
1048
+ bool: True, if request is successful, False otherwise.
1133
1049
  """
1134
1050
 
1135
- partitionPostBodyJson = {"name": name, "description": description}
1051
+ user_to_group_post_body_json = {"stringList": [group]}
1136
1052
 
1137
- request_url = self.partition_url()
1053
+ request_url = self.users_url() + "/" + user + "/memberof"
1138
1054
 
1139
1055
  logger.debug(
1140
- "Adding user partition -> %s (%s); calling -> %s",
1141
- name,
1142
- description,
1056
+ "Adding user -> '%s' to group -> '%s'; calling -> %s",
1057
+ user,
1058
+ group,
1143
1059
  request_url,
1144
1060
  )
1145
1061
 
1146
- retries = 0
1147
- while True:
1148
- response = requests.post(
1149
- url=request_url,
1150
- json=partitionPostBodyJson,
1151
- headers=REQUEST_HEADERS,
1152
- cookies=self.cookie(),
1153
- timeout=None,
1154
- )
1155
- if response.ok:
1156
- return self.parse_request_response(response)
1157
- # Check if Session has expired - then re-authenticate and try once more
1158
- elif response.status_code == 401 and retries == 0:
1159
- logger.debug("Session has expired - try to re-authenticate...")
1160
- self.authenticate(revalidate=True)
1161
- retries += 1
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 get_partition(self, name: str, show_error: bool = True) -> dict | None:
1174
- """Get an existing user partition from OTDS
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
- name (str): name of the partition to retrieve
1178
- show_error (bool, optional): whether or not we want to log an error
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
- dict: Request response or None if the REST call fails.
1088
+ bool: True, if request is successful, False otherwise.
1182
1089
  """
1183
1090
 
1184
- request_url = "{}/{}".format(self.config()["partitionUrl"], name)
1091
+ group_to_parent_group_post_body_json = {"stringList": [parent_group]}
1185
1092
 
1186
- logger.debug("Getting user partition -> %s; calling -> %s", name, request_url)
1093
+ request_url = self.groups_url() + "/" + group + "/memberof"
1187
1094
 
1188
- retries = 0
1189
- while True:
1190
- response = requests.get(
1191
- url=request_url,
1192
- headers=REQUEST_HEADERS,
1193
- cookies=self.cookie(),
1194
- timeout=None,
1195
- )
1196
- if response.ok:
1197
- return self.parse_request_response(response)
1198
- # Check if Session has expired - then re-authenticate and try once more
1199
- elif response.status_code == 401 and retries == 0:
1200
- logger.debug("Session has expired - try to re-authenticate...")
1201
- self.authenticate(revalidate=True)
1202
- retries += 1
1203
- else:
1204
- if show_error:
1205
- logger.error(
1206
- "Failed to get partition -> %s; warning -> %s (%s)",
1207
- name,
1208
- response.text,
1209
- response.status_code,
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 add_user(
1121
+ def add_resource(
1216
1122
  self,
1217
- partition: str,
1218
1123
  name: str,
1219
1124
  description: str = "",
1220
- first_name: str = "",
1221
- last_name: str = "",
1222
- email: str = "",
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 a new user to a user partition in OTDS
1131
+ """Add an OTDS resource
1225
1132
 
1226
1133
  Args:
1227
- partition (str): name of the OTDS user partition (needs to exist)
1228
- name (str): login name of the new user
1229
- description (str, optional): description of the new user
1230
- first_name (str, optional): first name of the new user
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 creation fails.
1139
+ dict: Request response (dictionary) or None if the REST call fails.
1235
1140
  """
1236
1141
 
1237
- userPostBodyJson = {
1238
- "userPartitionID": partition,
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
- request_url = self.users_url()
1249
-
1250
- logger.debug(
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
- if response.ok:
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
- def get_user(self, partition: str, user_id: str) -> dict | None:
1286
- """Get a user by its partition and user ID
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
- Args:
1289
- partition (str): name of the partition
1290
- user_id (str): ID of the user (= login name)
1291
- Returns:
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.users_url() + "/" + user_id + "@" + partition
1169
+ request_url = self.config()["resourceUrl"]
1296
1170
 
1297
1171
  logger.debug(
1298
- "Get user -> %s in partition -> %s; calling -> %s",
1299
- user_id,
1300
- partition,
1172
+ "Adding resource -> '%s' ('%s'); calling -> %s",
1173
+ name,
1174
+ description,
1301
1175
  request_url,
1302
1176
  )
1303
1177
 
1304
- retries = 0
1305
- while True:
1306
- response = requests.get(
1307
- url=request_url,
1308
- headers=REQUEST_HEADERS,
1309
- cookies=self.cookie(),
1310
- timeout=None,
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 get_users(self, partition: str = "", limit: int | None = None) -> dict | None:
1331
- """Get all users in a partition partition
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
- partition (str, optional): name of the partition
1335
- limit (int): maximum number of users to return
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 user was not found.
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
- retries = 0
1368
- while True:
1369
- response = requests.get(
1370
- url=request_url,
1371
- headers=REQUEST_HEADERS,
1372
- cookies=self.cookie(),
1373
- timeout=None,
1374
- )
1375
- if response.ok:
1376
- return self.parse_request_response(response)
1377
- # Check if Session has expired - then re-authenticate and try once more
1378
- elif response.status_code == 401 and retries == 0:
1379
- logger.debug("Session has expired - try to re-authenticate...")
1380
- self.authenticate(revalidate=True)
1381
- retries += 1
1382
- else:
1383
- if partition:
1384
- logger.error(
1385
- "Failed to get users in partition -> %s; error -> %s (%s)",
1386
- partition,
1387
- response.text,
1388
- response.status_code,
1389
- )
1390
- else:
1391
- logger.error(
1392
- "Failed to get users; error -> %s (%s)",
1393
- response.text,
1394
- response.status_code,
1395
- )
1396
- return None
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 update_user(
1401
- self, partition: str, user_id: str, attribute_name: str, attribute_value: str
1242
+ def update_resource(
1243
+ self, name: str, resource: object, show_error: bool = True
1402
1244
  ) -> dict | None:
1403
- """Update a user attribute with a new value
1245
+ """Update an existing OTDS resource
1404
1246
 
1405
1247
  Args:
1406
- partition (str): name of the partition
1407
- user_id (str): ID of the user (= login name)
1408
- attribute_name (str): name of the attribute
1409
- attribute_value (str): new value of the attribute
1410
- Return:
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
- if attribute_name in ["description"]:
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
- request_url = self.users_url() + "/" + user_id
1257
+ logger.debug("Updating resource -> '%s'; calling -> %s", name, request_url)
1426
1258
 
1427
- logger.debug(
1428
- "Update user -> %s attribute -> %s to value -> %s; calling -> %s",
1429
- user_id,
1430
- attribute_name,
1431
- attribute_value,
1432
- request_url,
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 delete_user(self, partition: str, user_id: str) -> bool:
1466
- """Delete an existing user
1270
+ def activate_resource(self, resource_id: str) -> dict | None:
1271
+ """Activate an OTDS resource
1467
1272
 
1468
1273
  Args:
1469
- partition (str): name of the partition
1470
- user_id (str): Id (= login name) of the user
1274
+ resource_id (str): ID of the OTDS resource
1471
1275
  Returns:
1472
- bool: True = success, False = error
1276
+ dict: Request response (json) or None if the REST call fails.
1473
1277
  """
1474
1278
 
1475
- request_url = self.users_url() + "/" + user_id + "@" + partition
1279
+ resource_post_body_json = {}
1280
+
1281
+ request_url = "{}/{}/activate".format(self.config()["resourceUrl"], resource_id)
1476
1282
 
1477
1283
  logger.debug(
1478
- "Delete user -> %s in partition -> %s; calling -> %s",
1479
- user_id,
1480
- partition,
1481
- request_url,
1284
+ "Activating resource -> '%s'; calling -> %s", resource_id, request_url
1482
1285
  )
1483
1286
 
1484
- retries = 0
1485
- while True:
1486
- response = requests.delete(
1487
- url=request_url,
1488
- headers=REQUEST_HEADERS,
1489
- cookies=self.cookie(),
1490
- timeout=None,
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 reset_user_password(self, user_id: str, password: str) -> bool:
1511
- """Reset a password of an existing user
1297
+ def get_access_roles(self) -> dict | None:
1298
+ """Get a list of all OTDS access roles
1512
1299
 
1513
1300
  Args:
1514
- user_id (str): Id (= login name) of the user
1515
- password (str): new password of the user
1301
+ None
1516
1302
  Returns:
1517
- bool: True = success, False = error.
1303
+ dict: Request response or None if the REST call fails.
1518
1304
  """
1519
1305
 
1520
- userPostBodyJson = {"newPassword": password}
1306
+ request_url = self.config()["accessRoleUrl"]
1521
1307
 
1522
- request_url = "{}/{}/password".format(self.users_url(), user_id)
1308
+ logger.debug("Retrieving access roles; calling -> %s", request_url)
1523
1309
 
1524
- logger.debug(
1525
- "Resetting password for user -> %s; calling -> %s", user_id, request_url
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
- retries = 0
1529
- while True:
1530
- response = requests.put(
1531
- url=request_url,
1532
- json=userPostBodyJson,
1533
- headers=REQUEST_HEADERS,
1534
- cookies=self.cookie(),
1535
- timeout=None,
1536
- )
1537
- if response.ok:
1538
- return True
1539
- # Check if Session has expired - then re-authenticate and try once more
1540
- elif response.status_code == 401 and retries == 0:
1541
- logger.debug("Session has expired - try to re-authenticate...")
1542
- self.authenticate(revalidate=True)
1543
- retries += 1
1544
- else:
1545
- logger.error(
1546
- "Failed to reset password for user -> %s; error -> %s (%s)",
1547
- user_id,
1548
- response.text,
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 add_group(self, partition: str, name: str, description: str) -> dict | None:
1556
- """Add a new user group to a user partition in OTDS
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
- partition (str): name of the OTDS user partition (needs to exist)
1560
- name (str): name of the new group
1561
- description (str): description of the new group
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
- dict: Request response (json) or None if the creation fails.
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
- groupPostBodyJson = {
1567
- "userPartitionID": partition,
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 = self.groups_url()
1360
+ request_url = "{}/{}/members".format(
1361
+ self.config()["accessRoleUrl"], access_role
1362
+ )
1573
1363
 
1574
1364
  logger.debug(
1575
- "Adding group -> %s to partition -> %s; calling -> %s",
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
- retries = 0
1583
- while True:
1584
- response = requests.post(
1585
- url=request_url,
1586
- json=groupPostBodyJson,
1587
- headers=REQUEST_HEADERS,
1588
- cookies=self.cookie(),
1589
- timeout=None,
1590
- )
1591
- if response.ok:
1592
- return self.parse_request_response(response)
1593
- # Check if Session has expired - then re-authenticate and try once more
1594
- elif response.status_code == 401 and retries == 0:
1595
- logger.debug("Session has expired - try to re-authenticate...")
1596
- self.authenticate(revalidate=True)
1597
- retries += 1
1598
- else:
1599
- logger.error(
1600
- "Failed to add group -> %s; error -> %s (%s)",
1601
- name,
1602
- response.text,
1603
- response.status_code,
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 None
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 get_group(self, group: str) -> dict | None:
1610
- """Get a OTDS group by its group name
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
- group (str): ID of the group (= group name)
1614
- Return:
1615
- dict: Request response or None if the group was not found.
1616
- Example values:
1617
- {
1618
- 'numMembers': 7,
1619
- 'userPartitionID': 'Content Server Members',
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
- request_url = self.groups_url() + "/" + group
1635
-
1636
- logger.debug("Get group -> %s; calling -> %s", group, request_url)
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
- retries = 0
1639
- while True:
1640
- response = requests.get(
1641
- url=request_url,
1642
- headers=REQUEST_HEADERS,
1643
- cookies=self.cookie(),
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
- response.text,
1658
- response.status_code,
1487
+ access_role,
1659
1488
  )
1660
- return None
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
- Args:
1668
- user (str): name of the OTDS user (needs to exist)
1669
- group (str): name of the OTDS group (needs to exist)
1670
- Returns:
1671
- bool: True, if request is successful, False otherwise.
1672
- """
1491
+ logger.debug(
1492
+ "Group -> '%s' is not yet in access role -> '%s' - adding...",
1493
+ group,
1494
+ access_role,
1495
+ )
1673
1496
 
1674
- userToGroupPostBodyJson = {"stringList": [group]}
1497
+ # create payload for REST call:
1498
+ access_role_post_body_json = {"groups": [{"name": group, "location": location}]}
1675
1499
 
1676
- request_url = self.users_url() + "/" + user + "/memberof"
1500
+ request_url = "{}/{}/members".format(
1501
+ self.config()["accessRoleUrl"], access_role
1502
+ )
1677
1503
 
1678
1504
  logger.debug(
1679
- "Adding user -> %s to group -> %s; calling -> %s", user, group, request_url
1505
+ "Add group -> '%s' to access role -> '%s'; calling -> %s",
1506
+ group,
1507
+ access_role,
1508
+ request_url,
1680
1509
  )
1681
1510
 
1682
- retries = 0
1683
- while True:
1684
- response = requests.post(
1685
- url=request_url,
1686
- json=userToGroupPostBodyJson,
1687
- headers=REQUEST_HEADERS,
1688
- cookies=self.cookie(),
1689
- timeout=None,
1690
- )
1691
- if response.ok:
1692
- return True
1693
- # Check if Session has expired - then re-authenticate and try once more
1694
- elif response.status_code == 401 and retries == 0:
1695
- logger.debug("Session has expired - try to re-authenticate...")
1696
- self.authenticate(revalidate=True)
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 add_group_to_parent_group(self, group: str, parent_group: str) -> bool:
1711
- """Add an existing group to an existing parent group in OTDS
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
- group (str): name of the OTDS group (needs to exist)
1715
- parent_group (str): name of the OTDS parent group (needs to exist)
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
- bool: True, if request is successful, False otherwise.
1540
+ dict: Request response (json) or None if the REST call fails.
1718
1541
  """
1719
1542
 
1720
- groupToParentGroupPostBodyJson = {"stringList": [parent_group]}
1543
+ # Return if list is empty:
1544
+ if not attribute_list:
1545
+ return None
1721
1546
 
1722
- request_url = self.groups_url() + "/" + group + "/memberof"
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
- "Adding group -> %s to parent group -> %s; calling -> %s",
1726
- group,
1727
- parent_group,
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
- retries = 0
1732
- while True:
1733
- response = requests.post(
1734
- url=request_url,
1735
- json=groupToParentGroupPostBodyJson,
1736
- headers=REQUEST_HEADERS,
1737
- cookies=self.cookie(),
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 add_resource(
1574
+ def add_license_to_resource(
1761
1575
  self,
1762
- name: str,
1763
- description: str,
1764
- display_name: str,
1765
- additional_payload: dict | None = None,
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
- name (str): name of the new OTDS resource
1771
- description (str): description of the new OTDS resource
1772
- display_name (str): display name of the OTDS resource
1773
- additional_payload (dict, optional): additional values for the json payload
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
- resourcePostBodyJson = {
1779
- "resourceName": name,
1780
- "description": description,
1781
- "displayName": display_name,
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
- # Check if there's additional payload for the body provided to handle special cases:
1785
- if additional_payload:
1786
- # Merge additional payload:
1787
- resourcePostBodyJson.update(additional_payload)
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.config()["resourceUrl"]
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 resource -> %s (%s); calling -> %s", name, description, request_url
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
- retries = 0
1796
- while True:
1797
- response = requests.post(
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
- json=resourcePostBodyJson,
1800
- headers=REQUEST_HEADERS,
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 get_resource(self, name: str, show_error: bool = False) -> dict | None:
1823
- """Get an existing OTDS resource
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
- name (str): name of the new OTDS resource
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
- dict: Request response or None if the REST call fails.
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 = "{}/{}".format(self.config()["resourceUrl"], name)
1685
+ request_url = (
1686
+ self.license_url()
1687
+ + "/assignedlicenses?resourceID="
1688
+ + resource_id
1689
+ + "&validOnly=false"
1690
+ )
1833
1691
 
1834
- logger.debug("Retrieving resource -> %s; calling -> %s", name, request_url)
1692
+ logger.debug(
1693
+ "Get license for resource -> %s; calling -> %s", resource_id, request_url
1694
+ )
1835
1695
 
1836
- retries = 0
1837
- while True:
1838
- response = requests.get(
1839
- url=request_url,
1840
- headers=REQUEST_HEADERS,
1841
- cookies=self.cookie(),
1842
- timeout=None,
1843
- )
1844
- if response.ok:
1845
- return self.parse_request_response(response)
1846
- # Check if Session has expired - then re-authenticate and try once more
1847
- elif response.status_code == 401 and retries == 0:
1848
- logger.debug("Session has expired - try to re-authenticate...")
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 update_resource(
1867
- self, name: str, resource: object, show_error: bool = True
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
- name (str): name of the new OTDS resource
1873
- resource (object): updated resource object of get_resource called before
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
- dict: Request response (json) or None if the REST call fails.
1719
+ bool: True if successful or False if the REST call fails
1877
1720
  """
1878
1721
 
1879
- request_url = "{}/{}".format(self.config()["resourceUrl"], name)
1722
+ request_url = "{}/{}".format(self.license_url(), license_id)
1880
1723
 
1881
- logger.debug("Updating resource -> %s; calling -> %s", name, request_url)
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
- retries = 0
1884
- while True:
1885
- response = requests.put(
1886
- url=request_url,
1887
- json=resource,
1888
- headers=REQUEST_HEADERS,
1889
- cookies=self.cookie(),
1890
- timeout=None,
1891
- )
1892
- if response.ok:
1893
- return self.parse_request_response(response)
1894
- # Check if Session has expired - then re-authenticate and try once more
1895
- elif response.status_code == 401 and retries == 0:
1896
- logger.debug("Session has expired - try to re-authenticate...")
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 activate_resource(self, resource_id: str) -> dict | None:
1915
- """Activate an OTDS resource
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
- resource_id (str): ID of the OTDS resource
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
- dict: Request response (json) or None if the REST call fails.
1767
+ bool: True if successful or False if the REST call fails or the license is not found
1921
1768
  """
1922
1769
 
1923
- resourcePostBodyJson = {}
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
- request_url = "{}/{}/activate".format(self.config()["resourceUrl"], resource_id)
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
- "Activating resource -> %s; calling -> %s", resource_id, request_url
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
- retries = 0
1932
- while True:
1933
- response = requests.post(
1934
- url=request_url,
1935
- json=resourcePostBodyJson,
1936
- headers=REQUEST_HEADERS,
1937
- cookies=self.cookie(),
1938
- timeout=None,
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
- if response.ok:
1941
- return self.parse_request_response(response)
1942
- # Check if Session has expired - then re-authenticate and try once more
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 get_access_roles(self) -> dict | None:
1959
- """Get a list of all OTDS access roles
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
- None
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
- dict: Request response or None if the REST call fails.
1851
+ bool: True if successful or False if the REST call fails or the license is not found
1965
1852
  """
1966
1853
 
1967
- request_url = self.config()["accessRoleUrl"]
1968
-
1969
- logger.debug("Retrieving access roles; calling -> %s", request_url)
1970
-
1971
- retries = 0
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
- if response.ok:
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
- # end method definition
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
- def get_access_role(self, access_role: str) -> dict | None:
1997
- """Get an OTDS access role
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
- Args:
2000
- name (str): name of the access role
2001
- Returns:
2002
- dict: Request response (json) or None if the REST call fails.
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.config()["accessRoleUrl"] + "/" + access_role
1895
+ request_url = self.license_url() + "/object/" + license_location
2006
1896
 
2007
1897
  logger.debug(
2008
- "Retrieving access role -> %s; calling -> %s", access_role, request_url
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
- retries = 0
2012
- while True:
2013
- response = requests.get(
2014
- url=request_url,
2015
- headers=REQUEST_HEADERS,
2016
- cookies=self.cookie(),
2017
- timeout=None,
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
- if response.ok:
2020
- return self.parse_request_response(response)
2021
- # Check if Session has expired - then re-authenticate and try once more
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 add_partition_to_access_role(
2038
- self, access_role: str, partition: str, location: str = ""
2039
- ) -> bool:
2040
- """Add an OTDS partition to an OTDS access role
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
- access_role (str): name of the OTDS access role
2044
- partition (str): name of the partition
2045
- location (str, optional): this is kind of a unique identifier DN (Distinguished Name)
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
- bool: True if partition is in access role or has been successfully added.
2049
- False if partition has been not been added (error)
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
- accessRolePostBodyJson = {
2053
- "userPartitions": [{"name": partition, "location": location}]
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
- request_url = "{}/{}/members".format(
2057
- self.config()["accessRoleUrl"], access_role
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
- "Add user partition -> %s to access role -> %s; calling -> %s",
2062
- partition,
2063
- access_role,
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
- retries = 0
2068
- while True:
2069
- response = requests.post(
2070
- url=request_url,
2071
- json=accessRolePostBodyJson,
2072
- headers=REQUEST_HEADERS,
2073
- cookies=self.cookie(),
2074
- timeout=None,
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 add_user_to_access_role(
2095
- self, access_role: str, user_id: str, location: str = ""
2019
+ def is_user_licensed(
2020
+ self, user_name: str, resource_id: str, license_feature: str, license_name: str
2096
2021
  ) -> bool:
2097
- """Add an OTDS user to an OTDS access role
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
- access_role (str): name of the OTDS access role
2101
- user_id (str): ID of the user (= login name)
2102
- location (str, optional): this is kind of a unique identifier DN (Distinguished Name)
2103
- most of the times you will want to keep it to empty string ("")
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 in access role or has been successfully added.
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
- # get existing members to check if user is already a member:
2110
- accessRolesGetResponse = self.get_access_role(access_role)
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 accessRolesGetResponse:
2040
+ if not response or not response["listUsersResults"]:
2113
2041
  return False
2114
2042
 
2115
- # Checking if user already added to access role
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
- logger.debug(
2127
- "User -> %s is not yet in access role -> %s - adding...",
2128
- user_id,
2129
- access_role,
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
- # create payload for REST call:
2133
- accessRolePostBodyJson = {"users": [{"name": user_id, "location": location}]}
2053
+ if user:
2054
+ return True
2134
2055
 
2135
- request_url = "{}/{}/members".format(
2136
- self.config()["accessRoleUrl"], access_role
2137
- )
2056
+ return False
2138
2057
 
2139
- logger.debug(
2140
- "Add user -> %s to access role -> %s; calling -> %s",
2141
- user_id,
2142
- access_role,
2143
- request_url,
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
- retries = 0
2147
- while True:
2148
- response = requests.post(
2149
- url=request_url,
2150
- json=accessRolePostBodyJson,
2151
- headers=REQUEST_HEADERS,
2152
- cookies=self.cookie(),
2153
- timeout=None,
2154
- )
2155
- if response.ok:
2156
- return True
2157
- elif response.status_code == 401 and retries == 0:
2158
- logger.debug("Session has expired - try to re-authenticate...")
2159
- self.authenticate(revalidate=True)
2160
- retries += 1
2161
- else:
2162
- logger.error(
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 add_group_to_access_role(
2174
- self, access_role: str, group: str, location: str = ""
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
- """Add an OTDS group to an OTDS access role
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
- access_role (str): name of the OTDS access role
2180
- group (str): name of the group
2181
- location (str, optional): this is kind of a unique identifier DN (Distinguished Name)
2182
- most of the times you will want to keep it to empty string ("")
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 group is in access role or has been successfully added.
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
- # get existing members to check if user is already a member:
2189
- accessRolesGetResponse = self.get_access_role(access_role)
2190
- if not accessRolesGetResponse:
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
- # Checking if group already added to access role
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
- logger.debug(
2203
- "Group -> %s is not yet in access role -> %s - adding...",
2204
- group,
2205
- access_role,
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
- # create payload for REST call:
2209
- accessRolePostBodyJson = {"groups": [{"name": group, "location": location}]}
2139
+ if partition:
2140
+ return True
2210
2141
 
2211
- request_url = "{}/{}/members".format(
2212
- self.config()["accessRoleUrl"], access_role
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
- "Add group -> %s to access role -> %s; calling -> %s",
2217
- group,
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=accessRolePostBodyJson,
2165
+ json=command,
2227
2166
  headers=REQUEST_HEADERS,
2228
2167
  cookies=self.cookie(),
2229
2168
  timeout=None,
2230
2169
  )
2231
- if response.ok:
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 add group -> %s to access role -> %s; error -> %s (%s)",
2240
- group,
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 False
2246
-
2247
- # end method definition
2248
-
2249
- def update_access_role_attributes(
2250
- self, name: str, attribute_list: list
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 existing access role
2256
- attribute_list (list): list of attribute name and attribute value pairs
2257
- The values need to be a list as well. Example:
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 (json) or None if the REST call fails.
2196
+ dict: Request response or None if the creation fails.
2261
2197
  """
2262
-
2263
- # Return if list is empty:
2264
- if not attribute_list:
2265
- return None
2266
-
2267
- # create payload for REST call:
2268
- access_role = self.get_access_role(name)
2269
- if not access_role:
2270
- logger.error("Failed to get access role -> %s", name)
2271
- return None
2272
-
2273
- accessRolePutBodyJson = {"attributes": attribute_list}
2274
-
2275
- request_url = "{}/{}/attributes".format(self.config()["accessRoleUrl"], name)
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
- "Update access role -> %s with attributes -> %s; calling -> %s",
2213
+ "Adding synchronized partition -> %s (%s); calling -> %s",
2279
2214
  name,
2280
- accessRolePutBodyJson,
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.put(
2221
+ response = requests.post(
2287
2222
  url=request_url,
2288
- json=accessRolePutBodyJson,
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 update access role -> %s; error -> %s (%s)",
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
- systemAttributePostBodyJson = {
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
- retries = 0
2349
- while True:
2350
- response = requests.post(
2351
- url=request_url,
2352
- json=systemAttributePostBodyJson,
2353
- headers=REQUEST_HEADERS,
2354
- cookies=self.cookie(),
2355
- timeout=None,
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("Retrieving trusted sites; calling -> %s", request_url)
2306
+ logger.debug("Get trusted sites; calling -> %s", request_url)
2388
2307
 
2389
- retries = 0
2390
- while True:
2391
- response = requests.get(
2392
- url=request_url,
2393
- headers=REQUEST_HEADERS,
2394
- cookies=self.cookie(),
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
- trustedSitePostBodyJson = {"stringList": [trusted_site]}
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
- existingTrustedSites = self.get_trusted_sites()
2330
+ existing_trusted_sites = self.get_trusted_sites()
2428
2331
 
2429
- if existingTrustedSites:
2430
- trustedSitePostBodyJson["stringList"].extend(
2431
- existingTrustedSites["stringList"]
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("Add trusted site -> %s; calling -> %s", trusted_site, request_url)
2339
+ logger.debug(
2340
+ "Add trusted site -> '%s'; calling -> %s", trusted_site, request_url
2341
+ )
2437
2342
 
2438
- response = requests.put(
2343
+ response = self.do_request(
2439
2344
  url=request_url,
2440
- json=trustedSitePostBodyJson,
2441
- headers=REQUEST_HEADERS,
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 # don't parse it!
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
- auditPutBodyJson = {
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
- response = requests.put(
2427
+ return self.do_request(
2527
2428
  url=request_url,
2528
- json=auditPutBodyJson,
2529
- headers=REQUEST_HEADERS,
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
- oauthClientPostBodyJson = {
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
- oauthClientPostBodyJson["secret"] = secret
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
- retries = 0
2653
- while True:
2654
- response = requests.post(
2655
- url=request_url,
2656
- json=oauthClientPostBodyJson,
2657
- headers=REQUEST_HEADERS,
2658
- cookies=self.cookie(),
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
- retries = 0
2694
- while True:
2695
- response = requests.get(
2696
- url=request_url,
2697
- headers=REQUEST_HEADERS,
2698
- cookies=self.cookie(),
2699
- timeout=None,
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
- oauthClientPatchBodyJson = updates
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
- retries = 0
2744
- while True:
2745
- response = requests.patch(
2746
- url=request_url,
2747
- json=oauthClientPatchBodyJson,
2748
- headers=REQUEST_HEADERS,
2749
- cookies=self.cookie(),
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
- retries = 0
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
- userPartitions = accessRolesJson["accessRoleMembers"]["userPartitions"]
2813
- for userPartition in userPartitions:
2814
- if userPartition["userPartition"] == "OAuthClients":
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
- partitionsResponse = requests.get(
2653
+
2654
+ response = self.do_request(
2825
2655
  url=request_url,
2826
- headers=REQUEST_HEADERS,
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 partitionsResponse.ok:
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
- oauthClientsOuBlock = {
2846
- "location": oauthClientLocation,
2847
- "name": oauthClientLocation,
2668
+ oauth_clients_ou_block = {
2669
+ "location": oauth_client_location,
2670
+ "name": oauth_client_location,
2848
2671
  "userPartition": None,
2849
2672
  }
2850
- accessRolesJson["accessRoleMembers"]["organizationalUnits"].append(
2851
- oauthClientsOuBlock
2673
+ access_role["accessRoleMembers"]["organizationalUnits"].append(
2674
+ oauth_clients_ou_block
2852
2675
  )
2853
2676
 
2854
- response = requests.put(
2677
+ return self.do_request(
2855
2678
  url=request_url,
2856
- json=accessRolesJson,
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
- retries = 0
2970
- while True:
2971
- response = requests.get(
2972
- url=request_url,
2973
- headers=REQUEST_HEADERS,
2974
- cookies=self.cookie(),
2975
- timeout=None,
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
- authHandlerPostBodyJson = {
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
- retries = 0
3325
- while True:
3326
- response = requests.post(
3327
- url=request_url,
3328
- json=authHandlerPostBodyJson,
3329
- headers=REQUEST_HEADERS,
3330
- cookies=self.cookie(),
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
- authHandlerPostBodyJson = {
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 = requests.post(
3224
+ response = self.do_request(
3446
3225
  url=request_url,
3447
- json=authHandlerPostBodyJson,
3448
- headers=REQUEST_HEADERS,
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 certFile:
3468
- certContent = certFile.read()
3469
- if not certContent:
3470
- logger.error("No data in certificate file -> %s", certificate_file)
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
- certContentDecoded = base64.b64decode(certContent, validate=True)
3486
- certContentEncoded = base64.b64encode(certContentDecoded).decode("utf-8")
3487
- if certContentEncoded == certContent.decode("utf-8"):
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 certFile:
3506
- certFile.write(base64.b64decode(certContent))
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
- authHandlerPostData = {
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
- authHandlerPostFiles = {
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=authHandlerPostData,
3545
- files=authHandlerPostFiles,
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
- authHandlerPostBodyJson = {
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
- retries = 0
3955
- while True:
3956
- response = requests.post(
3957
- url=request_url,
3958
- json=authHandlerPostBodyJson,
3959
- headers=REQUEST_HEADERS,
3960
- cookies=self.cookie(),
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("Resource -> %s not found - cannot consolidate", resource_name)
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
- consolidationPostBodyJson = {
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", resource_dn, request_url
3773
+ "Consolidation of resource -> %s (%s); calling -> %s",
3774
+ resource_name,
3775
+ resource_dn,
3776
+ request_url,
4011
3777
  )
4012
3778
 
4013
- retries = 0
4014
- while True:
4015
- response = requests.post(
4016
- url=request_url,
4017
- json=consolidationPostBodyJson,
4018
- headers=REQUEST_HEADERS,
4019
- cookies=self.cookie(),
4020
- timeout=None,
4021
- )
4022
- if response.ok:
4023
- return True
4024
- # Check if Session has expired - then re-authenticate and try once more
4025
- elif response.status_code == 401 and retries == 0:
4026
- logger.debug("Session has expired - try to re-authenticate...")
4027
- self.authenticate(revalidate=True)
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
- impersonationPutBodyJson = {
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
- retries = 0
4074
- while True:
4075
- response = requests.put(
4076
- url=request_url,
4077
- json=impersonationPutBodyJson,
4078
- headers=REQUEST_HEADERS,
4079
- cookies=self.cookie(),
4080
- timeout=None,
4081
- )
4082
- if response.ok:
4083
- return True
4084
- # Check if Session has expired - then re-authenticate and try once more
4085
- elif response.status_code == 401 and retries == 0:
4086
- logger.debug("Session has expired - try to re-authenticate...")
4087
- self.authenticate(revalidate=True)
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
- impersonationPutBodyJson = {
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
- retries = 0
4133
- while True:
4134
- response = requests.put(
4135
- url=request_url,
4136
- json=impersonationPutBodyJson,
4137
- headers=REQUEST_HEADERS,
4138
- cookies=self.cookie(),
4139
- timeout=None,
4140
- )
4141
- if response.ok:
4142
- return True
4143
- # Check if Session has expired - then re-authenticate and try once more
4144
- elif response.status_code == 401 and retries == 0:
4145
- logger.debug("Session has expired - try to re-authenticate...")
4146
- self.authenticate(revalidate=True)
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
- retries = 0
4193
- while True:
4194
- response = requests.get(
4195
- url=request_url,
4196
- headers=REQUEST_HEADERS,
4197
- cookies=self.cookie(),
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
- retries = 0
4258
- while True:
4259
- response = requests.put(
4260
- url=request_url,
4261
- json=update_values,
4262
- headers=REQUEST_HEADERS,
4263
- cookies=self.cookie(),
4264
- timeout=None,
4265
- )
4266
- if response.ok:
4267
- return True
4268
- # Check if Session has expired - then re-authenticate and try once more
4269
- elif response.status_code == 401 and retries == 0:
4270
- logger.debug("Session has expired - try to re-authenticate...")
4271
- self.authenticate(revalidate=True)
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