pyxecm 3.0.1__py3-none-any.whl → 3.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyxecm might be problematic. Click here for more details.

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