pyxecm 1.6__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +6 -4
- pyxecm/avts.py +673 -246
- pyxecm/coreshare.py +686 -467
- pyxecm/customizer/__init__.py +16 -4
- pyxecm/customizer/__main__.py +58 -0
- pyxecm/customizer/api/__init__.py +5 -0
- pyxecm/customizer/api/__main__.py +6 -0
- pyxecm/customizer/api/app.py +914 -0
- pyxecm/customizer/api/auth.py +154 -0
- pyxecm/customizer/api/metrics.py +92 -0
- pyxecm/customizer/api/models.py +13 -0
- pyxecm/customizer/api/payload_list.py +865 -0
- pyxecm/customizer/api/settings.py +103 -0
- pyxecm/customizer/browser_automation.py +332 -139
- pyxecm/customizer/customizer.py +1007 -1130
- pyxecm/customizer/exceptions.py +35 -0
- pyxecm/customizer/guidewire.py +322 -0
- pyxecm/customizer/k8s.py +713 -378
- pyxecm/customizer/log.py +107 -0
- pyxecm/customizer/m365.py +2867 -909
- pyxecm/customizer/nhc.py +1169 -0
- pyxecm/customizer/openapi.py +258 -0
- pyxecm/customizer/payload.py +16817 -7467
- pyxecm/customizer/pht.py +699 -285
- pyxecm/customizer/salesforce.py +516 -342
- pyxecm/customizer/sap.py +58 -41
- pyxecm/customizer/servicenow.py +593 -371
- pyxecm/customizer/settings.py +442 -0
- pyxecm/customizer/successfactors.py +408 -346
- pyxecm/customizer/translate.py +83 -48
- pyxecm/helper/__init__.py +5 -2
- pyxecm/helper/assoc.py +83 -43
- pyxecm/helper/data.py +2406 -870
- pyxecm/helper/logadapter.py +27 -0
- pyxecm/helper/web.py +229 -101
- pyxecm/helper/xml.py +527 -171
- pyxecm/maintenance_page/__init__.py +5 -0
- pyxecm/maintenance_page/__main__.py +6 -0
- pyxecm/maintenance_page/app.py +51 -0
- pyxecm/maintenance_page/settings.py +28 -0
- pyxecm/maintenance_page/static/favicon.avif +0 -0
- pyxecm/maintenance_page/templates/maintenance.html +165 -0
- pyxecm/otac.py +234 -140
- pyxecm/otawp.py +1436 -557
- pyxecm/otcs.py +7716 -3161
- pyxecm/otds.py +2150 -919
- pyxecm/otiv.py +36 -21
- pyxecm/otmm.py +1272 -325
- pyxecm/otpd.py +231 -127
- pyxecm-2.0.0.dist-info/METADATA +145 -0
- pyxecm-2.0.0.dist-info/RECORD +54 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
- pyxecm-1.6.dist-info/METADATA +0 -53
- pyxecm-1.6.dist-info/RECORD +0 -32
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/top_level.txt +0 -0
pyxecm/otds.py
CHANGED
|
@@ -1,130 +1,57 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Class: OTDS
|
|
8
|
-
Methods:
|
|
9
|
-
|
|
10
|
-
__init__ : class initializer
|
|
11
|
-
config : returns config data set
|
|
12
|
-
cookie : returns cookie information
|
|
13
|
-
credentials: returns set of username and password
|
|
14
|
-
|
|
15
|
-
base_url : returns OTDS base URL
|
|
16
|
-
rest_url : returns OTDS REST base URL
|
|
17
|
-
credential_url : returns the OTDS Credentials REST URL
|
|
18
|
-
authHandler_url : returns the OTDS Authentication Handler REST URL
|
|
19
|
-
partition_url : returns OTDS Partition REST URL
|
|
20
|
-
access_role_url : returns OTDS Access Role REST URL
|
|
21
|
-
oauth_client_url : returns OTDS OAuth Client REST URL
|
|
22
|
-
resource_url : returns OTDS Resource REST URL
|
|
23
|
-
license_url : returns OTDS License REST URL
|
|
24
|
-
token_url : returns OTDS Token REST URL
|
|
25
|
-
users_url : returns OTDS Users REST URL
|
|
26
|
-
groups_url : returns OTDS Groups REST URL
|
|
27
|
-
system_config_url : returns OTDS System Config REST URL
|
|
28
|
-
consolidation_url: returns OTDS consolidation URL
|
|
29
|
-
|
|
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
|
|
32
|
-
|
|
33
|
-
authenticate : authenticates at OTDS server
|
|
34
|
-
|
|
35
|
-
add_synchronized_partition: Add a Synchronized partition to OTDS
|
|
36
|
-
add_partition : Add an OTDS partition
|
|
37
|
-
get_partition : Get a partition with a specific name
|
|
38
|
-
|
|
39
|
-
add_user : Add a user to a partion
|
|
40
|
-
get_user : Get a user with a specific user ID (= login name @ partition)
|
|
41
|
-
get_users: get all users (with option to filter)
|
|
42
|
-
update_user : Update attributes of on OTDS user
|
|
43
|
-
delete_user : Delete a user with a specific ID in a specific partition
|
|
44
|
-
reset_user_password : Reset a password of a specific user ID
|
|
45
|
-
|
|
46
|
-
add_group: Add an OTDS group
|
|
47
|
-
get_group: Get a OTDS group by its name
|
|
48
|
-
add_user_to_group : Add an OTDS user to a OTDS group
|
|
49
|
-
add_group_to_parent_group : Add on OTDS group to a parent group
|
|
50
|
-
|
|
51
|
-
add_resource : Add a new resource to OTDS
|
|
52
|
-
get_resource : Get an OTDS resource with a specific name
|
|
53
|
-
update_resource: Update an existing OTDS resource
|
|
54
|
-
activate_resource : Activate an OTDS resource
|
|
55
|
-
|
|
56
|
-
get_access_roles : Get all OTDS Access Roles
|
|
57
|
-
get_access_role: Get an OTDS Access Role with a specific name
|
|
58
|
-
add_partition_to_access_role : Add an OTDS Partition to to an OTDS Access Role
|
|
59
|
-
add_user_to_access_role : Add an OTDS user to to an OTDS Access Role
|
|
60
|
-
add_group_to_access_role : Add an OTDS group to to an OTDS Access Role
|
|
61
|
-
update_access_role_attributes: Update attributes of an existing access role
|
|
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
|
-
|
|
77
|
-
add_system_attribute : Add an OTDS System Attribute
|
|
78
|
-
|
|
79
|
-
get_trusted_sites : Get OTDS Trusted Sites
|
|
80
|
-
add_trusted_site : Add a new trusted site to OTDS
|
|
81
|
-
|
|
82
|
-
enable_audit: enable OTDS audit
|
|
83
|
-
|
|
84
|
-
add_oauth_client : Add a new OAuth client to OTDS
|
|
85
|
-
get_oauth_client : Get an OAuth client with a specific client ID
|
|
86
|
-
update_oauth_client : Update an OAuth client
|
|
87
|
-
add_oauth_clients_to_access_role : Add an OTDS OAuth Client to an OTDS Access Role
|
|
88
|
-
get_access_token : Get an OTDS Access Token
|
|
89
|
-
|
|
90
|
-
get_auth_handler: Gen an auth handler with a given name
|
|
91
|
-
add_auth_handler_saml: Add an authentication handler for SAML (e.g. for SuccessFactors)
|
|
92
|
-
add_auth_handler_sap: Add an authentication handler for SAP
|
|
93
|
-
add_auth_handler_oauth: Add an authentication handler for OAuth (used for Salesforce)
|
|
94
|
-
|
|
95
|
-
consolidate: Consolidate an OTDS resource
|
|
96
|
-
impersonate_resource: Configure impersonation for an OTDS resource
|
|
97
|
-
impersonate_oauth_client: Configure impersonation for an OTDS OAuth Client
|
|
98
|
-
|
|
99
|
-
get_password_policy: get the global password policy
|
|
100
|
-
update_password_policy: updates the global password policy
|
|
1
|
+
"""OTDS Module to implement functions to read / write OTDS objects.
|
|
2
|
+
|
|
3
|
+
This includes Ressources, Users, Groups, Licenses, Trusted Sites, OAuth Clients, ...
|
|
4
|
+
|
|
5
|
+
The documentation for the used REST APIs can be found here:
|
|
6
|
+
- [https://developer.opentext.com](https://developer.opentext.com/ce/products/opentext-directory-services)
|
|
101
7
|
|
|
8
|
+
|
|
9
|
+
!!! tip
|
|
10
|
+
Important: userIDs consists of login name + "@" + partition name
|
|
102
11
|
"""
|
|
103
12
|
|
|
104
13
|
__author__ = "Dr. Marc Diefenbruch"
|
|
105
|
-
__copyright__ = "Copyright 2024, OpenText"
|
|
106
|
-
__credits__ = ["Kai-Philip Gatzweiler"
|
|
14
|
+
__copyright__ = "Copyright (C) 2024-2025, OpenText"
|
|
15
|
+
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
107
16
|
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
108
17
|
__email__ = "mdiefenb@opentext.com"
|
|
109
18
|
|
|
110
|
-
import os
|
|
111
|
-
import logging
|
|
112
|
-
import json
|
|
113
|
-
import urllib.parse
|
|
114
19
|
import base64
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
import os
|
|
23
|
+
import platform
|
|
24
|
+
import sys
|
|
25
|
+
import tempfile
|
|
115
26
|
import time
|
|
116
|
-
|
|
27
|
+
import urllib.parse
|
|
117
28
|
from http import HTTPStatus
|
|
29
|
+
from importlib.metadata import version
|
|
30
|
+
|
|
118
31
|
import requests
|
|
119
32
|
|
|
120
|
-
|
|
33
|
+
APP_NAME = "pyxecm"
|
|
34
|
+
APP_VERSION = version("pyxecm")
|
|
35
|
+
MODULE_NAME = APP_NAME + ".otds"
|
|
36
|
+
|
|
37
|
+
PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
38
|
+
OS_INFO = f"{platform.system()} {platform.release()}"
|
|
39
|
+
ARCH_INFO = platform.machine()
|
|
40
|
+
REQUESTS_VERSION = requests.__version__
|
|
41
|
+
|
|
42
|
+
USER_AGENT = (
|
|
43
|
+
f"{APP_NAME}/{APP_VERSION} ({MODULE_NAME}/{APP_VERSION}; "
|
|
44
|
+
f"Python/{PYTHON_VERSION}; {OS_INFO}; {ARCH_INFO}; Requests/{REQUESTS_VERSION})"
|
|
45
|
+
)
|
|
121
46
|
|
|
122
47
|
REQUEST_HEADERS = {
|
|
48
|
+
"User-Agent": USER_AGENT,
|
|
123
49
|
"accept": "application/json;charset=utf-8",
|
|
124
50
|
"Content-Type": "application/json",
|
|
125
51
|
}
|
|
126
52
|
|
|
127
53
|
REQUEST_FORM_HEADERS = {
|
|
54
|
+
"User-Agent": USER_AGENT,
|
|
128
55
|
"accept": "application/json;charset=utf-8",
|
|
129
56
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
130
57
|
}
|
|
@@ -133,9 +60,13 @@ REQUEST_TIMEOUT = 60
|
|
|
133
60
|
REQUEST_RETRY_DELAY = 20
|
|
134
61
|
REQUEST_MAX_RETRIES = 2
|
|
135
62
|
|
|
63
|
+
default_logger = logging.getLogger(MODULE_NAME)
|
|
64
|
+
|
|
136
65
|
|
|
137
66
|
class OTDS:
|
|
138
|
-
"""
|
|
67
|
+
"""Class OTDS is used to automate stettings in OpenText Directory Services (OTDS)."""
|
|
68
|
+
|
|
69
|
+
logger: logging.Logger = default_logger
|
|
139
70
|
|
|
140
71
|
_config = None
|
|
141
72
|
_cookie = None
|
|
@@ -149,19 +80,35 @@ class OTDS:
|
|
|
149
80
|
username: str | None = None,
|
|
150
81
|
password: str | None = None,
|
|
151
82
|
otds_ticket: str | None = None,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
83
|
+
bind_password: str | None = None,
|
|
84
|
+
logger: logging.Logger = default_logger,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Initialize the OTDS object.
|
|
155
87
|
|
|
156
88
|
Args:
|
|
157
|
-
protocol (str):
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
89
|
+
protocol (str):
|
|
90
|
+
This is either http or https.
|
|
91
|
+
hostname (str):
|
|
92
|
+
The hostname of OTDS.
|
|
93
|
+
port (int):
|
|
94
|
+
The port number - typically 80 or 443.
|
|
95
|
+
username (str, optional):
|
|
96
|
+
The OTDS user name. Optional if otds_ticket is provided.
|
|
97
|
+
password (str, optional):
|
|
98
|
+
The OTDS password. Optional if otds_ticket is provided.
|
|
99
|
+
otds_ticket (str | None, optional):
|
|
100
|
+
Authentication ticket of OTDS.
|
|
101
|
+
bind_password (str | None, optional): TODO
|
|
102
|
+
logger (logging.Logger, optional):
|
|
103
|
+
The logging object to use for all log messages. Defaults to default_logger.
|
|
104
|
+
|
|
163
105
|
"""
|
|
164
106
|
|
|
107
|
+
if logger != default_logger:
|
|
108
|
+
self.logger = logger.getChild("otds")
|
|
109
|
+
for logfilter in logger.filters:
|
|
110
|
+
self.logger.addFilter(logfilter)
|
|
111
|
+
|
|
165
112
|
# Initialize otdsConfig as an empty dictionary
|
|
166
113
|
otds_config = {}
|
|
167
114
|
|
|
@@ -189,67 +136,99 @@ class OTDS:
|
|
|
189
136
|
otds_config["password"] = password
|
|
190
137
|
else:
|
|
191
138
|
otds_config["password"] = ""
|
|
192
|
-
|
|
193
|
-
if
|
|
194
|
-
otds_config["bindPassword"] =
|
|
139
|
+
|
|
140
|
+
if bind_password:
|
|
141
|
+
otds_config["bindPassword"] = bind_password
|
|
195
142
|
else:
|
|
196
143
|
otds_config["bindPassword"] = ""
|
|
197
144
|
|
|
198
145
|
if otds_ticket:
|
|
199
146
|
self._cookie = {"OTDSTicket": otds_ticket}
|
|
200
147
|
|
|
201
|
-
|
|
148
|
+
otds_base_url = protocol + "://" + otds_config["hostname"]
|
|
202
149
|
if str(port) not in ["80", "443"]:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
otds_config["baseUrl"] =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
otds_config["restUrl"] =
|
|
209
|
-
|
|
210
|
-
otds_config["partitionUrl"] =
|
|
211
|
-
otds_config["identityproviderprofiles"] =
|
|
212
|
-
otds_config["accessRoleUrl"] =
|
|
213
|
-
otds_config["credentialUrl"] =
|
|
214
|
-
otds_config["
|
|
215
|
-
otds_config["
|
|
216
|
-
otds_config["
|
|
217
|
-
otds_config["
|
|
218
|
-
otds_config["
|
|
219
|
-
otds_config["
|
|
220
|
-
otds_config["
|
|
221
|
-
otds_config["
|
|
222
|
-
otds_config["
|
|
150
|
+
otds_base_url += ":{}".format(port)
|
|
151
|
+
otds_base_url += "/otdsws"
|
|
152
|
+
otds_config["baseUrl"] = otds_base_url
|
|
153
|
+
|
|
154
|
+
otds_rest_url = otds_base_url + "/rest"
|
|
155
|
+
otds_config["restUrl"] = otds_rest_url
|
|
156
|
+
|
|
157
|
+
otds_config["partitionUrl"] = otds_rest_url + "/partitions"
|
|
158
|
+
otds_config["identityproviderprofiles"] = otds_rest_url + "/identityproviderprofiles"
|
|
159
|
+
otds_config["accessRoleUrl"] = otds_rest_url + "/accessroles"
|
|
160
|
+
otds_config["credentialUrl"] = otds_rest_url + "/authentication/credentials"
|
|
161
|
+
otds_config["ticketforuserUrl"] = otds_rest_url + "/authentication/ticketforuser"
|
|
162
|
+
otds_config["oauthClientUrl"] = otds_rest_url + "/oauthclients"
|
|
163
|
+
otds_config["tokenUrl"] = otds_base_url + "/oauth2/token"
|
|
164
|
+
otds_config["resourceUrl"] = otds_rest_url + "/resources"
|
|
165
|
+
otds_config["licenseUrl"] = otds_rest_url + "/licensemanagement/licenses"
|
|
166
|
+
otds_config["usersUrl"] = otds_rest_url + "/users"
|
|
167
|
+
otds_config["currentUserUrl"] = otds_rest_url + "/currentuser"
|
|
168
|
+
otds_config["groupsUrl"] = otds_rest_url + "/groups"
|
|
169
|
+
otds_config["systemConfigUrl"] = otds_rest_url + "/systemconfig"
|
|
170
|
+
otds_config["authHandlerUrl"] = otds_rest_url + "/authhandlers"
|
|
171
|
+
otds_config["consolidationUrl"] = otds_rest_url + "/consolidation"
|
|
172
|
+
otds_config["rolesUrl"] = otds_rest_url + "/roles"
|
|
223
173
|
|
|
224
174
|
self._config = otds_config
|
|
225
175
|
|
|
226
176
|
def config(self) -> dict:
|
|
227
|
-
"""
|
|
177
|
+
"""Return the configuration dictionary.
|
|
228
178
|
|
|
229
179
|
Returns:
|
|
230
|
-
dict:
|
|
180
|
+
dict:
|
|
181
|
+
The configuration dictionary.
|
|
182
|
+
|
|
231
183
|
"""
|
|
232
184
|
return self._config
|
|
233
185
|
|
|
234
186
|
# end method definition
|
|
235
187
|
|
|
236
188
|
def cookie(self) -> dict:
|
|
237
|
-
"""
|
|
238
|
-
|
|
189
|
+
"""Return the login cookie of OTDS.
|
|
190
|
+
|
|
191
|
+
This is set by the authenticate() method
|
|
239
192
|
|
|
240
193
|
Returns:
|
|
241
|
-
dict:
|
|
194
|
+
dict:
|
|
195
|
+
The OTDS cookie.
|
|
196
|
+
|
|
242
197
|
"""
|
|
243
198
|
return self._cookie
|
|
244
199
|
|
|
245
200
|
# end method definition
|
|
246
201
|
|
|
202
|
+
def set_cookie(self, ticket: str) -> dict:
|
|
203
|
+
"""Return the login cookie of OTDS.
|
|
204
|
+
|
|
205
|
+
This is set by the authenticate() method
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
ticket (str):
|
|
209
|
+
The new ticket value for the cookie.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
dict:
|
|
213
|
+
The updated OTDS cookie.
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
self._cookie["OTDSTicket"] = ticket
|
|
218
|
+
|
|
219
|
+
return self._cookie
|
|
220
|
+
|
|
221
|
+
# end method definition
|
|
222
|
+
|
|
247
223
|
def credentials(self) -> dict:
|
|
248
|
-
"""
|
|
224
|
+
"""Return the credentials (username + password).
|
|
249
225
|
|
|
250
226
|
Returns:
|
|
251
|
-
dict:
|
|
227
|
+
dict:
|
|
228
|
+
The dictionary with username and password.
|
|
229
|
+
|
|
252
230
|
"""
|
|
231
|
+
|
|
253
232
|
return {
|
|
254
233
|
"userName": self.config()["username"],
|
|
255
234
|
"password": self.config()["password"],
|
|
@@ -258,149 +237,208 @@ class OTDS:
|
|
|
258
237
|
# end method definition
|
|
259
238
|
|
|
260
239
|
def base_url(self) -> str:
|
|
261
|
-
"""
|
|
240
|
+
"""Return the base URL of OTDS.
|
|
262
241
|
|
|
263
242
|
Returns:
|
|
264
|
-
str:
|
|
243
|
+
str:
|
|
244
|
+
The base URL.
|
|
245
|
+
|
|
265
246
|
"""
|
|
247
|
+
|
|
266
248
|
return self.config()["baseUrl"]
|
|
267
249
|
|
|
268
250
|
# end method definition
|
|
269
251
|
|
|
270
252
|
def rest_url(self) -> str:
|
|
271
|
-
"""
|
|
253
|
+
"""Return the REST URL of OTDS.
|
|
272
254
|
|
|
273
255
|
Returns:
|
|
274
|
-
str:
|
|
256
|
+
str:
|
|
257
|
+
The REST URL.
|
|
258
|
+
|
|
275
259
|
"""
|
|
260
|
+
|
|
276
261
|
return self.config()["restUrl"]
|
|
277
262
|
|
|
278
263
|
# end method definition
|
|
279
264
|
|
|
280
265
|
def credential_url(self) -> str:
|
|
281
|
-
"""
|
|
266
|
+
"""Return the Credentials URL of OTDS.
|
|
282
267
|
|
|
283
268
|
Returns:
|
|
284
|
-
str:
|
|
269
|
+
str:
|
|
270
|
+
The credentials URL.
|
|
271
|
+
|
|
285
272
|
"""
|
|
273
|
+
|
|
286
274
|
return self.config()["credentialUrl"]
|
|
287
275
|
|
|
288
276
|
# end method definition
|
|
289
277
|
|
|
290
278
|
def auth_handler_url(self) -> str:
|
|
291
|
-
"""
|
|
279
|
+
"""Return the Auth Handler URL of OTDS.
|
|
292
280
|
|
|
293
281
|
Returns:
|
|
294
|
-
str:
|
|
282
|
+
str:
|
|
283
|
+
The auth handler URL.
|
|
284
|
+
|
|
295
285
|
"""
|
|
286
|
+
|
|
296
287
|
return self.config()["authHandlerUrl"]
|
|
297
288
|
|
|
298
289
|
# end method definition
|
|
299
290
|
def synchronized_partition_url(self) -> str:
|
|
300
|
-
"""
|
|
291
|
+
"""Return the Partition URL of OTDS.
|
|
301
292
|
|
|
302
293
|
Returns:
|
|
303
|
-
str:
|
|
294
|
+
str:
|
|
295
|
+
The synchronized partition URL.
|
|
296
|
+
|
|
304
297
|
"""
|
|
298
|
+
|
|
305
299
|
return self.config()["identityproviderprofiles"]
|
|
306
|
-
|
|
300
|
+
|
|
301
|
+
# end of method definition
|
|
307
302
|
|
|
308
303
|
def partition_url(self) -> str:
|
|
309
|
-
"""
|
|
304
|
+
"""Return the partition URL of OTDS.
|
|
310
305
|
|
|
311
306
|
Returns:
|
|
312
|
-
str:
|
|
307
|
+
str:
|
|
308
|
+
The partition URL.
|
|
309
|
+
|
|
313
310
|
"""
|
|
311
|
+
|
|
314
312
|
return self.config()["partitionUrl"]
|
|
315
313
|
|
|
316
314
|
# end method definition
|
|
317
315
|
|
|
318
316
|
def access_role_url(self) -> str:
|
|
319
|
-
"""
|
|
317
|
+
"""Return the access role URL of OTDS.
|
|
320
318
|
|
|
321
319
|
Returns:
|
|
322
|
-
str:
|
|
320
|
+
str:
|
|
321
|
+
The access role URL.
|
|
322
|
+
|
|
323
323
|
"""
|
|
324
|
+
|
|
324
325
|
return self.config()["accessRoleUrl"]
|
|
325
326
|
|
|
326
327
|
# end method definition
|
|
327
328
|
|
|
328
329
|
def oauth_client_url(self) -> str:
|
|
329
|
-
"""
|
|
330
|
+
"""Return the OAuth client URL of OTDS.
|
|
330
331
|
|
|
331
332
|
Returns:
|
|
332
|
-
str:
|
|
333
|
+
str:
|
|
334
|
+
The OAuth client URL.
|
|
335
|
+
|
|
333
336
|
"""
|
|
337
|
+
|
|
334
338
|
return self.config()["oauthClientUrl"]
|
|
335
339
|
|
|
336
340
|
# end method definition
|
|
337
341
|
|
|
338
342
|
def resource_url(self) -> str:
|
|
339
|
-
"""
|
|
343
|
+
"""Return the resource URL of OTDS.
|
|
340
344
|
|
|
341
345
|
Returns:
|
|
342
|
-
str:
|
|
346
|
+
str:
|
|
347
|
+
The resource URL.
|
|
348
|
+
|
|
343
349
|
"""
|
|
350
|
+
|
|
344
351
|
return self.config()["resourceUrl"]
|
|
345
352
|
|
|
346
353
|
# end method definition
|
|
347
354
|
|
|
348
355
|
def license_url(self) -> str:
|
|
349
|
-
"""
|
|
356
|
+
"""Return the License URL of OTDS.
|
|
350
357
|
|
|
351
358
|
Returns:
|
|
352
|
-
str:
|
|
359
|
+
str:
|
|
360
|
+
The license URL.
|
|
361
|
+
|
|
353
362
|
"""
|
|
363
|
+
|
|
354
364
|
return self.config()["licenseUrl"]
|
|
355
365
|
|
|
356
366
|
# end method definition
|
|
357
367
|
|
|
358
368
|
def token_url(self) -> str:
|
|
359
|
-
"""
|
|
369
|
+
"""Return the token URL of OTDS.
|
|
360
370
|
|
|
361
371
|
Returns:
|
|
362
|
-
str:
|
|
372
|
+
str:
|
|
373
|
+
The token URL.
|
|
374
|
+
|
|
363
375
|
"""
|
|
376
|
+
|
|
364
377
|
return self.config()["tokenUrl"]
|
|
365
378
|
|
|
366
379
|
# end method definition
|
|
367
380
|
|
|
368
381
|
def users_url(self) -> str:
|
|
369
|
-
"""
|
|
382
|
+
"""Return the users URL of OTDS.
|
|
370
383
|
|
|
371
384
|
Returns:
|
|
372
|
-
str:
|
|
385
|
+
str:
|
|
386
|
+
The users URL.
|
|
387
|
+
|
|
373
388
|
"""
|
|
389
|
+
|
|
374
390
|
return self.config()["usersUrl"]
|
|
375
391
|
|
|
376
392
|
# end method definition
|
|
377
393
|
|
|
394
|
+
def current_user_url(self) -> str:
|
|
395
|
+
"""Return the current user URL of OTDS.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
str:
|
|
399
|
+
The current user URL.
|
|
400
|
+
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
return self.config()["currentUserUrl"]
|
|
404
|
+
|
|
405
|
+
# end method definition
|
|
406
|
+
|
|
378
407
|
def groups_url(self) -> str:
|
|
379
|
-
"""
|
|
408
|
+
"""Return the groups URL of OTDS.
|
|
380
409
|
|
|
381
410
|
Returns:
|
|
382
|
-
str:
|
|
411
|
+
str:
|
|
412
|
+
The groups URL.
|
|
413
|
+
|
|
383
414
|
"""
|
|
415
|
+
|
|
384
416
|
return self.config()["groupsUrl"]
|
|
385
417
|
|
|
386
418
|
# end method definition
|
|
387
419
|
|
|
388
420
|
def system_config_url(self) -> str:
|
|
389
|
-
"""
|
|
421
|
+
"""Return the system config URL of OTDS.
|
|
390
422
|
|
|
391
423
|
Returns:
|
|
392
|
-
str:
|
|
424
|
+
str:
|
|
425
|
+
The system config URL.
|
|
426
|
+
|
|
393
427
|
"""
|
|
428
|
+
|
|
394
429
|
return self.config()["systemConfigUrl"]
|
|
395
430
|
|
|
396
431
|
# end method definition
|
|
397
432
|
|
|
398
433
|
def consolidation_url(self) -> str:
|
|
399
|
-
"""
|
|
434
|
+
"""Return the consolidation URL of OTDS.
|
|
400
435
|
|
|
401
436
|
Returns:
|
|
402
|
-
str:
|
|
437
|
+
str:
|
|
438
|
+
The consolidation URL.
|
|
439
|
+
|
|
403
440
|
"""
|
|
441
|
+
|
|
404
442
|
return self.config()["consolidationUrl"]
|
|
405
443
|
|
|
406
444
|
# end method definition
|
|
@@ -423,27 +461,49 @@ class OTDS:
|
|
|
423
461
|
retry_forever: bool = False,
|
|
424
462
|
parse_request_response: bool = True,
|
|
425
463
|
) -> dict | None:
|
|
426
|
-
"""Call an OTDS REST API in a safe way
|
|
464
|
+
"""Call an OTDS REST API in a safe way.
|
|
427
465
|
|
|
428
466
|
Args:
|
|
429
|
-
url (str):
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
467
|
+
url (str):
|
|
468
|
+
The URL to send the request to.
|
|
469
|
+
method (str, optional):
|
|
470
|
+
The HTTP method (GET, POST, etc.). Defaults to "GET".
|
|
471
|
+
headers (dict | None, optional):
|
|
472
|
+
The request headers. Defaults to None.
|
|
473
|
+
data (dict | None, optional):
|
|
474
|
+
Request payload. Defaults to None
|
|
475
|
+
json_data (dict | None, optional):
|
|
476
|
+
Request payload for the JSON parameter. Defaults to None.
|
|
477
|
+
files (dict | None, optional):
|
|
478
|
+
Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
479
|
+
File-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
480
|
+
timeout (int | None, optional):
|
|
481
|
+
The timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
482
|
+
show_error (bool, optional):
|
|
483
|
+
Whether or not an error should be logged in case of a failed REST call.
|
|
484
|
+
If False, then only a warning is logged. Defaults to True.
|
|
485
|
+
show_warning (bool, optional):
|
|
486
|
+
Whether or not an warning should be logged in case of a
|
|
487
|
+
failed REST call.
|
|
488
|
+
If False, then only a warning is logged. Defaults to True.
|
|
489
|
+
warning_message (str, optional):
|
|
490
|
+
Specific warning message. Defaults to "". If not given the error_message will be used.
|
|
491
|
+
failure_message (str, optional):
|
|
492
|
+
Specific error message. Defaults to "".
|
|
493
|
+
success_message (str, optional):
|
|
494
|
+
Specific success message. Defaults to "".
|
|
495
|
+
max_retries (int, optional):
|
|
496
|
+
How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
|
|
497
|
+
retry_forever (bool, optional):
|
|
498
|
+
Eventually wait forever - without timeout. Defaults to False.
|
|
499
|
+
parse_request_response (bool, optional):
|
|
500
|
+
Defines if the response.text should be interpreted as json and loaded into a dictionary.
|
|
501
|
+
True is the default.
|
|
444
502
|
|
|
445
503
|
Returns:
|
|
446
|
-
dict | None:
|
|
504
|
+
dict | None:
|
|
505
|
+
Response of OTDS REST API or None in case of an error.
|
|
506
|
+
|
|
447
507
|
"""
|
|
448
508
|
|
|
449
509
|
if headers is None:
|
|
@@ -469,26 +529,33 @@ class OTDS:
|
|
|
469
529
|
|
|
470
530
|
if response.ok:
|
|
471
531
|
if success_message:
|
|
472
|
-
logger.info(success_message)
|
|
532
|
+
self.logger.info(success_message)
|
|
473
533
|
if parse_request_response:
|
|
474
534
|
return self.parse_request_response(response)
|
|
475
535
|
else:
|
|
476
536
|
return response
|
|
477
537
|
# Check if Session has expired - then re-authenticate and try once more
|
|
478
|
-
elif
|
|
479
|
-
|
|
538
|
+
elif (
|
|
539
|
+
(response.status_code == 401 and retries == 0)
|
|
540
|
+
or (
|
|
541
|
+
response.status_code == 400
|
|
542
|
+
and retries == 0
|
|
543
|
+
and "Expired OTDS SSO ticket"
|
|
544
|
+
in response.text # OTDS seems to return 400 and not 401 for token expiry (in some cases like impersonation)
|
|
545
|
+
)
|
|
546
|
+
):
|
|
547
|
+
self.logger.debug("Session has expired - try to re-authenticate...")
|
|
480
548
|
self.authenticate(revalidate=True)
|
|
481
549
|
retries += 1
|
|
482
550
|
else:
|
|
483
551
|
# Handle plain HTML responses to not pollute the logs
|
|
484
552
|
content_type = response.headers.get("content-type", None)
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
response_text = response.text
|
|
553
|
+
response_text = (
|
|
554
|
+
"HTML content (only printed in debug log)" if content_type == "text/html" else response.text
|
|
555
|
+
)
|
|
489
556
|
|
|
490
557
|
if show_error:
|
|
491
|
-
logger.error(
|
|
558
|
+
self.logger.error(
|
|
492
559
|
"%s; status -> %s/%s; error -> %s",
|
|
493
560
|
failure_message,
|
|
494
561
|
response.status_code,
|
|
@@ -496,7 +563,7 @@ class OTDS:
|
|
|
496
563
|
response_text,
|
|
497
564
|
)
|
|
498
565
|
elif show_warning:
|
|
499
|
-
logger.warning(
|
|
566
|
+
self.logger.warning(
|
|
500
567
|
"%s; status -> %s/%s; warning -> %s",
|
|
501
568
|
warning_message if warning_message else failure_message,
|
|
502
569
|
response.status_code,
|
|
@@ -504,7 +571,7 @@ class OTDS:
|
|
|
504
571
|
response_text,
|
|
505
572
|
)
|
|
506
573
|
if content_type == "text/html":
|
|
507
|
-
logger.debug(
|
|
574
|
+
self.logger.debug(
|
|
508
575
|
"%s; status -> %s/%s; warning -> %s",
|
|
509
576
|
failure_message,
|
|
510
577
|
response.status_code,
|
|
@@ -514,45 +581,45 @@ class OTDS:
|
|
|
514
581
|
return None
|
|
515
582
|
except requests.exceptions.Timeout:
|
|
516
583
|
if retries <= max_retries:
|
|
517
|
-
logger.warning(
|
|
584
|
+
self.logger.warning(
|
|
518
585
|
"Request timed out. Retrying in %s seconds...",
|
|
519
586
|
str(REQUEST_RETRY_DELAY),
|
|
520
587
|
)
|
|
521
588
|
retries += 1
|
|
522
589
|
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
523
590
|
else:
|
|
524
|
-
logger.error(
|
|
525
|
-
"%s; timeout error",
|
|
591
|
+
self.logger.error(
|
|
592
|
+
"%s; timeout error.",
|
|
526
593
|
failure_message,
|
|
527
594
|
)
|
|
528
595
|
if retry_forever:
|
|
529
596
|
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
530
|
-
logger.warning("Turn timeouts off and wait forever...")
|
|
597
|
+
self.logger.warning("Turn timeouts off and wait forever...")
|
|
531
598
|
timeout = None
|
|
532
599
|
else:
|
|
533
600
|
return None
|
|
534
601
|
except requests.exceptions.ConnectionError:
|
|
535
602
|
if retries <= max_retries:
|
|
536
|
-
logger.warning(
|
|
603
|
+
self.logger.warning(
|
|
537
604
|
"Connection error. Retrying in %s seconds...",
|
|
538
605
|
str(REQUEST_RETRY_DELAY),
|
|
539
606
|
)
|
|
540
607
|
retries += 1
|
|
541
608
|
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
542
609
|
else:
|
|
543
|
-
logger.error(
|
|
544
|
-
"%s; connection error",
|
|
610
|
+
self.logger.error(
|
|
611
|
+
"%s; connection error.",
|
|
545
612
|
failure_message,
|
|
546
613
|
)
|
|
547
614
|
if retry_forever:
|
|
548
615
|
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
549
|
-
logger.warning("Turn timeouts off and wait forever...")
|
|
616
|
+
self.logger.warning("Turn timeouts off and wait forever...")
|
|
550
617
|
timeout = None
|
|
551
618
|
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
552
619
|
else:
|
|
553
620
|
return None
|
|
554
621
|
# end try
|
|
555
|
-
logger.debug(
|
|
622
|
+
self.logger.debug(
|
|
556
623
|
"Retrying REST API %s call -> %s... (retry = %s, cookie -> %s)",
|
|
557
624
|
method,
|
|
558
625
|
url,
|
|
@@ -569,22 +636,27 @@ class OTDS:
|
|
|
569
636
|
additional_error_message: str = "",
|
|
570
637
|
show_error: bool = True,
|
|
571
638
|
) -> dict | None:
|
|
572
|
-
"""
|
|
573
|
-
that also handles exceptions.
|
|
639
|
+
"""Convert the request response to a dict in a safe way that also handles exceptions.
|
|
574
640
|
|
|
575
641
|
Args:
|
|
576
|
-
response_object (object):
|
|
577
|
-
|
|
578
|
-
|
|
642
|
+
response_object (object):
|
|
643
|
+
This is reponse object delivered by the request call.
|
|
644
|
+
additional_error_message (str, optional):
|
|
645
|
+
Print a custom error message.
|
|
646
|
+
show_error (bool, optional):
|
|
647
|
+
If True, log an error, if False log a warning.
|
|
648
|
+
|
|
579
649
|
Returns:
|
|
580
|
-
dict
|
|
650
|
+
dict | None:
|
|
651
|
+
Response dictionary or None in case of an error.
|
|
652
|
+
|
|
581
653
|
"""
|
|
582
654
|
|
|
583
655
|
if not response_object:
|
|
584
656
|
return None
|
|
585
657
|
|
|
586
658
|
if not response_object.text:
|
|
587
|
-
logger.warning("Response text is empty. Cannot decode response.")
|
|
659
|
+
self.logger.warning("Response text is empty. Cannot decode response.")
|
|
588
660
|
return None
|
|
589
661
|
|
|
590
662
|
try:
|
|
@@ -592,14 +664,15 @@ class OTDS:
|
|
|
592
664
|
except json.JSONDecodeError as e:
|
|
593
665
|
if additional_error_message:
|
|
594
666
|
message = "Cannot decode response as JSon. {}; error -> {}".format(
|
|
595
|
-
additional_error_message,
|
|
667
|
+
additional_error_message,
|
|
668
|
+
e,
|
|
596
669
|
)
|
|
597
670
|
else:
|
|
598
671
|
message = "Cannot decode response as JSon; error -> {}".format(e)
|
|
599
672
|
if show_error:
|
|
600
|
-
logger.error(message)
|
|
673
|
+
self.logger.error(message)
|
|
601
674
|
else:
|
|
602
|
-
logger.warning(message)
|
|
675
|
+
self.logger.warning(message)
|
|
603
676
|
return None
|
|
604
677
|
else:
|
|
605
678
|
return dict_object
|
|
@@ -607,18 +680,22 @@ class OTDS:
|
|
|
607
680
|
# end method definition
|
|
608
681
|
|
|
609
682
|
def authenticate(self, revalidate: bool = False) -> dict | None:
|
|
610
|
-
"""Authenticate at Directory Services and retrieve
|
|
683
|
+
"""Authenticate at Directory Services and retrieve OTDS ticket.
|
|
611
684
|
|
|
612
685
|
Args:
|
|
613
|
-
revalidate (bool, optional):
|
|
614
|
-
|
|
686
|
+
revalidate (bool, optional):
|
|
687
|
+
Determine if a re-athentication is enforced.
|
|
688
|
+
(e.g. if session has timed out with 401 error)
|
|
689
|
+
|
|
615
690
|
Returns:
|
|
616
|
-
dict
|
|
691
|
+
dict | None:
|
|
692
|
+
Cookie information. Also stores cookie information in self._cookie
|
|
693
|
+
|
|
617
694
|
"""
|
|
618
695
|
|
|
619
696
|
# Already authenticated and session still valid?
|
|
620
697
|
if self._cookie and not revalidate:
|
|
621
|
-
logger.debug(
|
|
698
|
+
self.logger.debug(
|
|
622
699
|
"Session still valid - return existing cookie -> %s",
|
|
623
700
|
str(self._cookie),
|
|
624
701
|
)
|
|
@@ -626,7 +703,7 @@ class OTDS:
|
|
|
626
703
|
|
|
627
704
|
otds_ticket = "NotSet"
|
|
628
705
|
|
|
629
|
-
logger.debug("Requesting OTDS ticket from -> %s", self.credential_url())
|
|
706
|
+
self.logger.debug("Requesting OTDS ticket from -> %s", self.credential_url())
|
|
630
707
|
|
|
631
708
|
response = None
|
|
632
709
|
try:
|
|
@@ -634,15 +711,14 @@ class OTDS:
|
|
|
634
711
|
url=self.credential_url(),
|
|
635
712
|
json=self.credentials(),
|
|
636
713
|
headers=REQUEST_HEADERS,
|
|
637
|
-
timeout=
|
|
714
|
+
timeout=REQUEST_TIMEOUT,
|
|
638
715
|
)
|
|
639
716
|
except requests.exceptions.RequestException as exception:
|
|
640
|
-
logger.warning(
|
|
641
|
-
"Unable to connect to -> %s
|
|
717
|
+
self.logger.warning(
|
|
718
|
+
"Unable to connect to OTDS authentication endpoint -> %s%s. OTDS service may not be ready yet.",
|
|
642
719
|
self.credential_url(),
|
|
643
|
-
exception
|
|
720
|
+
"; error -> {}".format(str(exception)) if str(exception) else "",
|
|
644
721
|
)
|
|
645
|
-
logger.warning("OTDS service may not be ready yet.")
|
|
646
722
|
return None
|
|
647
723
|
|
|
648
724
|
if response.ok:
|
|
@@ -651,9 +727,12 @@ class OTDS:
|
|
|
651
727
|
return None
|
|
652
728
|
else:
|
|
653
729
|
otds_ticket = authenticate_dict["ticket"]
|
|
654
|
-
logger.debug("Ticket -> %s", otds_ticket)
|
|
730
|
+
self.logger.debug("Ticket -> %s", otds_ticket)
|
|
655
731
|
else:
|
|
656
|
-
logger.error(
|
|
732
|
+
self.logger.error(
|
|
733
|
+
"Failed to request an OTDS ticket; error -> %s",
|
|
734
|
+
response.text,
|
|
735
|
+
)
|
|
657
736
|
return None
|
|
658
737
|
|
|
659
738
|
# Store authentication ticket:
|
|
@@ -664,21 +743,349 @@ class OTDS:
|
|
|
664
743
|
|
|
665
744
|
# end method definition
|
|
666
745
|
|
|
746
|
+
def impersonate_user(
|
|
747
|
+
self,
|
|
748
|
+
user_id: str,
|
|
749
|
+
partition: str = "Content Server Members",
|
|
750
|
+
ticket: str = "",
|
|
751
|
+
) -> dict | None:
|
|
752
|
+
"""Impersonate as a user.
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
partition (str):
|
|
756
|
+
The partition of the user.
|
|
757
|
+
user_id (str):
|
|
758
|
+
The ID (= login) of the user.
|
|
759
|
+
ticket (str, optional):
|
|
760
|
+
Optional, if the ticket to impersonate with is already known.
|
|
761
|
+
Defaults to "".
|
|
762
|
+
|
|
763
|
+
Returns:
|
|
764
|
+
dict | None:
|
|
765
|
+
Information about the impersonated user.
|
|
766
|
+
|
|
767
|
+
Example:
|
|
768
|
+
{
|
|
769
|
+
'token': None,
|
|
770
|
+
'userId': 'nwheeler@Content Server Members',
|
|
771
|
+
'ticket': '*OTDSSSO*Adh...*',
|
|
772
|
+
'resourceID': None,
|
|
773
|
+
'failureReason': None,
|
|
774
|
+
'passwordExpirationTime': 0,
|
|
775
|
+
'continuation': False,
|
|
776
|
+
'continuationContext': None,
|
|
777
|
+
'continuationData': None
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
"""
|
|
781
|
+
|
|
782
|
+
if not ticket:
|
|
783
|
+
ticket = self._otds_ticket
|
|
784
|
+
|
|
785
|
+
request_url = self.config()["ticketforuserUrl"]
|
|
786
|
+
|
|
787
|
+
impersonate_post_body = {
|
|
788
|
+
"userName": user_id + "@" + partition,
|
|
789
|
+
"ticket": ticket,
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
self.logger.debug(
|
|
793
|
+
"Impersonate user -> '%s' ; calling -> %s",
|
|
794
|
+
user_id,
|
|
795
|
+
request_url,
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
return self.do_request(
|
|
799
|
+
url=request_url,
|
|
800
|
+
method="POST",
|
|
801
|
+
json_data=impersonate_post_body,
|
|
802
|
+
timeout=None,
|
|
803
|
+
failure_message="Failed to impersonate as user -> '{}'".format(user_id),
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
# end method definition
|
|
807
|
+
|
|
808
|
+
def add_application_role(
|
|
809
|
+
self,
|
|
810
|
+
name: str,
|
|
811
|
+
partition_id: str = "OAuthClients",
|
|
812
|
+
description: str = "",
|
|
813
|
+
values: list | None = None,
|
|
814
|
+
custom_attributes: list | None = None,
|
|
815
|
+
) -> dict | None:
|
|
816
|
+
"""Add a new application role to partition.
|
|
817
|
+
|
|
818
|
+
Args:
|
|
819
|
+
name (str):
|
|
820
|
+
The name of the new partition.
|
|
821
|
+
partition_id (str, optional):
|
|
822
|
+
ID of the partition to add the role to, defaults to "OAuthClients".
|
|
823
|
+
description (str):
|
|
824
|
+
The description of the new partition.
|
|
825
|
+
values (list, optional):
|
|
826
|
+
List of optional values to pass with the create request.
|
|
827
|
+
custom_attributes (list, optional):
|
|
828
|
+
List of optional custom attributes to pass with the create request.
|
|
829
|
+
|
|
830
|
+
Returns:
|
|
831
|
+
dict | None:
|
|
832
|
+
Request response or None if the creation fails.
|
|
833
|
+
|
|
834
|
+
"""
|
|
835
|
+
|
|
836
|
+
if values is None:
|
|
837
|
+
values = []
|
|
838
|
+
role_post_body_json = {
|
|
839
|
+
"name": name,
|
|
840
|
+
"description": description,
|
|
841
|
+
"userPartitionID": partition_id,
|
|
842
|
+
"values": values if values else [],
|
|
843
|
+
"customAttributes": custom_attributes if custom_attributes else [],
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
request_url = self.config()["rolesUrl"]
|
|
847
|
+
|
|
848
|
+
self.logger.debug(
|
|
849
|
+
"Adding application role -> '%s' (%s) to partition -> '%s' ; calling -> %s",
|
|
850
|
+
name,
|
|
851
|
+
description,
|
|
852
|
+
partition_id,
|
|
853
|
+
request_url,
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
return self.do_request(
|
|
857
|
+
url=request_url,
|
|
858
|
+
method="POST",
|
|
859
|
+
json_data=role_post_body_json,
|
|
860
|
+
timeout=None,
|
|
861
|
+
failure_message="Failed to add application role -> '{}'".format(name),
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
# end method definition
|
|
865
|
+
|
|
866
|
+
def get_application_role(self, name: str, partition: str = "OAuthClients", show_error: bool = True) -> dict | None:
|
|
867
|
+
"""Get an existing application role from OTDS.
|
|
868
|
+
|
|
869
|
+
Args:
|
|
870
|
+
name (str):
|
|
871
|
+
The name of the application role to retrieve.
|
|
872
|
+
partition (str):
|
|
873
|
+
Partition of the application role.
|
|
874
|
+
show_error (bool, optional):
|
|
875
|
+
Defines whether or not we want to log an error
|
|
876
|
+
if the partition is not found.
|
|
877
|
+
|
|
878
|
+
Returns:
|
|
879
|
+
dict | None:
|
|
880
|
+
Request response or None if the REST call fails.
|
|
881
|
+
|
|
882
|
+
"""
|
|
883
|
+
|
|
884
|
+
request_url = "{}?where_filter={}".format(self.config()["rolesUrl"], name)
|
|
885
|
+
|
|
886
|
+
self.logger.debug(
|
|
887
|
+
"Get application Roles -> '%s'; calling -> %s",
|
|
888
|
+
name,
|
|
889
|
+
request_url,
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
response = self.do_request(
|
|
893
|
+
url=request_url,
|
|
894
|
+
method="GET",
|
|
895
|
+
timeout=None,
|
|
896
|
+
failure_message="Failed to get user partition -> '{}'".format(name),
|
|
897
|
+
show_error=show_error,
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
role = next(
|
|
901
|
+
(role for role in response.get("roles") if role["name"] == name and role["userPartitionID"] == partition),
|
|
902
|
+
None,
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
return role
|
|
906
|
+
|
|
907
|
+
# end method definition
|
|
908
|
+
|
|
909
|
+
def assign_user_to_application_role(
|
|
910
|
+
self,
|
|
911
|
+
user_id: str,
|
|
912
|
+
user_partition: str,
|
|
913
|
+
role_name: str,
|
|
914
|
+
role_partition: str = "OAuthClients",
|
|
915
|
+
) -> bool:
|
|
916
|
+
"""Assign an OTDS user to an application role in OTDS.
|
|
917
|
+
|
|
918
|
+
Args:
|
|
919
|
+
user_id (str):
|
|
920
|
+
The ID of the user (= login name) to assign to the license.
|
|
921
|
+
user_partition (str):
|
|
922
|
+
The user partition in OTDS, e.g. "Content Server Members".
|
|
923
|
+
role_name (str):
|
|
924
|
+
Name of the application role to be assigned.
|
|
925
|
+
role_partition (str):
|
|
926
|
+
The name of the partition of the Role, defaults to "OAuthClients".
|
|
927
|
+
|
|
928
|
+
Returns:
|
|
929
|
+
bool:
|
|
930
|
+
True if successful or False if the REST call fails or the license is not found.
|
|
931
|
+
|
|
932
|
+
"""
|
|
933
|
+
|
|
934
|
+
user = self.get_user(user_partition, user_id)
|
|
935
|
+
if user:
|
|
936
|
+
user_location = user["location"]
|
|
937
|
+
else:
|
|
938
|
+
self.logger.error("Cannot find location for user -> '%s'", user_id)
|
|
939
|
+
return False
|
|
940
|
+
|
|
941
|
+
role = self.get_application_role(role_name, role_partition)
|
|
942
|
+
if role:
|
|
943
|
+
rolelocation = role.get("location")
|
|
944
|
+
else:
|
|
945
|
+
self.logger.warning("Cannot find application role -> '%s' (%s)", role_name, role_partition)
|
|
946
|
+
return False
|
|
947
|
+
|
|
948
|
+
role_post_body_json = {
|
|
949
|
+
"stringList": [
|
|
950
|
+
rolelocation,
|
|
951
|
+
],
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
request_url = self.users_url() + "/" + user_location + "/roles"
|
|
955
|
+
|
|
956
|
+
self.logger.debug(
|
|
957
|
+
"Assign application role -> '%s' (%s) to user -> '%s' (%s); calling -> %s",
|
|
958
|
+
role_name,
|
|
959
|
+
role_partition,
|
|
960
|
+
user_id,
|
|
961
|
+
user_partition,
|
|
962
|
+
request_url,
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
response = self.do_request(
|
|
966
|
+
url=request_url,
|
|
967
|
+
method="POST",
|
|
968
|
+
json_data=role_post_body_json,
|
|
969
|
+
timeout=None,
|
|
970
|
+
failure_message="Failed to assign application role -> '{}' to user -> '{}'".format(
|
|
971
|
+
role_name,
|
|
972
|
+
user_id,
|
|
973
|
+
),
|
|
974
|
+
parse_request_response=False,
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
if response and response.ok:
|
|
978
|
+
self.logger.debug(
|
|
979
|
+
"Added application role -> '%s' to user -> '%s'",
|
|
980
|
+
role_name,
|
|
981
|
+
user_id,
|
|
982
|
+
)
|
|
983
|
+
return True
|
|
984
|
+
|
|
985
|
+
return False
|
|
986
|
+
|
|
987
|
+
# end method definition
|
|
988
|
+
|
|
989
|
+
def assign_group_to_application_role(
|
|
990
|
+
self,
|
|
991
|
+
group_id: str,
|
|
992
|
+
group_partition: str,
|
|
993
|
+
role_name: str,
|
|
994
|
+
role_partition: str = "OAuthClients",
|
|
995
|
+
) -> bool:
|
|
996
|
+
"""Assign an OTDS group to an application role in OTDS.
|
|
997
|
+
|
|
998
|
+
Args:
|
|
999
|
+
group_id (str):
|
|
1000
|
+
The ID of the group to assign to the application role.
|
|
1001
|
+
group_partition (str):
|
|
1002
|
+
The group partition in OTDS, e.g. "Content Server Members".
|
|
1003
|
+
role_name (str):
|
|
1004
|
+
Name of the application role to be assigned.
|
|
1005
|
+
role_partition (str):
|
|
1006
|
+
The name of the partition of the Role, defaults to "OAuthClients".
|
|
1007
|
+
|
|
1008
|
+
Returns:
|
|
1009
|
+
bool:
|
|
1010
|
+
True if successful or False if the REST call fails or the license is not found.
|
|
1011
|
+
|
|
1012
|
+
"""
|
|
1013
|
+
|
|
1014
|
+
group = self.get_group(group_id)
|
|
1015
|
+
if group:
|
|
1016
|
+
group_location = group["location"]
|
|
1017
|
+
else:
|
|
1018
|
+
self.logger.error("Cannot find location for group -> '%s'", group_id)
|
|
1019
|
+
return False
|
|
1020
|
+
|
|
1021
|
+
role = self.get_application_role(role_name, role_partition)
|
|
1022
|
+
if role:
|
|
1023
|
+
rolelocation = role.get("location")
|
|
1024
|
+
else:
|
|
1025
|
+
self.logger.warning("Cannot find application role -> '%s' (%s)", role_name, role_partition)
|
|
1026
|
+
return False
|
|
1027
|
+
|
|
1028
|
+
role_post_body_json = {
|
|
1029
|
+
"stringList": [
|
|
1030
|
+
rolelocation,
|
|
1031
|
+
],
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
request_url = self.groups_url() + "/" + group_location + "/roles"
|
|
1035
|
+
|
|
1036
|
+
self.logger.debug(
|
|
1037
|
+
"Assign application role -> '%s' (%s) to group -> '%s' (%s); calling -> %s",
|
|
1038
|
+
role_name,
|
|
1039
|
+
role_partition,
|
|
1040
|
+
group_id,
|
|
1041
|
+
group_partition,
|
|
1042
|
+
request_url,
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
response = self.do_request(
|
|
1046
|
+
url=request_url,
|
|
1047
|
+
method="POST",
|
|
1048
|
+
json_data=role_post_body_json,
|
|
1049
|
+
timeout=None,
|
|
1050
|
+
failure_message="Failed to assign application role -> '{}' to group -> '{}'".format(
|
|
1051
|
+
role_name,
|
|
1052
|
+
group_id,
|
|
1053
|
+
),
|
|
1054
|
+
parse_request_response=False,
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
if response and response.ok:
|
|
1058
|
+
self.logger.debug(
|
|
1059
|
+
"Added application role -> '%s' to group -> '%s'",
|
|
1060
|
+
role_name,
|
|
1061
|
+
group_id,
|
|
1062
|
+
)
|
|
1063
|
+
return True
|
|
1064
|
+
|
|
1065
|
+
return False
|
|
1066
|
+
|
|
1067
|
+
# end method definition
|
|
1068
|
+
|
|
667
1069
|
def add_partition(self, name: str, description: str) -> dict | None:
|
|
668
|
-
"""Add a new user partition to OTDS
|
|
1070
|
+
"""Add a new user partition to OTDS.
|
|
669
1071
|
|
|
670
1072
|
Args:
|
|
671
|
-
name (str):
|
|
672
|
-
|
|
1073
|
+
name (str):
|
|
1074
|
+
The name of the new partition.
|
|
1075
|
+
description (str):
|
|
1076
|
+
The description of the new partition.
|
|
1077
|
+
|
|
673
1078
|
Returns:
|
|
674
|
-
dict
|
|
1079
|
+
dict | None:
|
|
1080
|
+
Request response or None if the creation fails.
|
|
1081
|
+
|
|
675
1082
|
"""
|
|
676
1083
|
|
|
677
1084
|
partition_post_body_json = {"name": name, "description": description}
|
|
678
1085
|
|
|
679
1086
|
request_url = self.partition_url()
|
|
680
1087
|
|
|
681
|
-
logger.debug(
|
|
1088
|
+
self.logger.debug(
|
|
682
1089
|
"Adding user partition -> '%s' (%s); calling -> %s",
|
|
683
1090
|
name,
|
|
684
1091
|
description,
|
|
@@ -696,19 +1103,28 @@ class OTDS:
|
|
|
696
1103
|
# end method definition
|
|
697
1104
|
|
|
698
1105
|
def get_partition(self, name: str, show_error: bool = True) -> dict | None:
|
|
699
|
-
"""Get an existing user partition from OTDS
|
|
1106
|
+
"""Get an existing user partition from OTDS.
|
|
700
1107
|
|
|
701
1108
|
Args:
|
|
702
|
-
name (str):
|
|
703
|
-
|
|
704
|
-
|
|
1109
|
+
name (str):
|
|
1110
|
+
The name of the partition to retrieve.
|
|
1111
|
+
show_error (bool, optional):
|
|
1112
|
+
Defines whether or not we want to log an error
|
|
1113
|
+
if the partition is not found.
|
|
1114
|
+
|
|
705
1115
|
Returns:
|
|
706
|
-
dict
|
|
1116
|
+
dict | None:
|
|
1117
|
+
Request response or None if the REST call fails.
|
|
1118
|
+
|
|
707
1119
|
"""
|
|
708
1120
|
|
|
709
1121
|
request_url = "{}/{}".format(self.config()["partitionUrl"], name)
|
|
710
1122
|
|
|
711
|
-
logger.debug(
|
|
1123
|
+
self.logger.debug(
|
|
1124
|
+
"Get user partition -> '%s'; calling -> %s",
|
|
1125
|
+
name,
|
|
1126
|
+
request_url,
|
|
1127
|
+
)
|
|
712
1128
|
|
|
713
1129
|
return self.do_request(
|
|
714
1130
|
url=request_url,
|
|
@@ -729,17 +1145,26 @@ class OTDS:
|
|
|
729
1145
|
last_name: str = "",
|
|
730
1146
|
email: str = "",
|
|
731
1147
|
) -> dict | None:
|
|
732
|
-
"""Add a new user to a user partition in OTDS
|
|
1148
|
+
"""Add a new user to a user partition in OTDS.
|
|
733
1149
|
|
|
734
1150
|
Args:
|
|
735
|
-
partition (str):
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1151
|
+
partition (str):
|
|
1152
|
+
The name of the OTDS user partition (needs to exist).
|
|
1153
|
+
name (str):
|
|
1154
|
+
The login name of the new user.
|
|
1155
|
+
description (str, optional):
|
|
1156
|
+
The description of the new user. Default is empty string.
|
|
1157
|
+
first_name (str, optional):
|
|
1158
|
+
The optional first name of the new user.
|
|
1159
|
+
last_name (str, optional):
|
|
1160
|
+
The optional last name of the new user.
|
|
1161
|
+
email (str, optional):
|
|
1162
|
+
The email address of the new user.
|
|
1163
|
+
|
|
741
1164
|
Returns:
|
|
742
|
-
dict
|
|
1165
|
+
dict | None:
|
|
1166
|
+
Request response or None if the creation fails.
|
|
1167
|
+
|
|
743
1168
|
"""
|
|
744
1169
|
|
|
745
1170
|
user_post_body_json = {
|
|
@@ -755,13 +1180,13 @@ class OTDS:
|
|
|
755
1180
|
|
|
756
1181
|
request_url = self.users_url()
|
|
757
1182
|
|
|
758
|
-
logger.debug(
|
|
1183
|
+
self.logger.debug(
|
|
759
1184
|
"Adding user -> '%s' to partition -> '%s'; calling -> %s",
|
|
760
1185
|
name,
|
|
761
1186
|
partition,
|
|
762
1187
|
request_url,
|
|
763
1188
|
)
|
|
764
|
-
logger.debug("User Attributes -> %s", str(user_post_body_json))
|
|
1189
|
+
self.logger.debug("User Attributes -> %s", str(user_post_body_json))
|
|
765
1190
|
|
|
766
1191
|
return self.do_request(
|
|
767
1192
|
url=request_url,
|
|
@@ -774,18 +1199,23 @@ class OTDS:
|
|
|
774
1199
|
# end method definition
|
|
775
1200
|
|
|
776
1201
|
def get_user(self, partition: str, user_id: str) -> dict | None:
|
|
777
|
-
"""Get
|
|
1202
|
+
"""Get an existing user by its partition and user ID.
|
|
778
1203
|
|
|
779
1204
|
Args:
|
|
780
|
-
partition (str):
|
|
781
|
-
|
|
1205
|
+
partition (str):
|
|
1206
|
+
The name of the partition the user is in.
|
|
1207
|
+
user_id (str):
|
|
1208
|
+
The ID of the user (= login name).
|
|
1209
|
+
|
|
782
1210
|
Returns:
|
|
783
|
-
dict
|
|
1211
|
+
dict | None:
|
|
1212
|
+
Request response or None if the user was not found.
|
|
1213
|
+
|
|
784
1214
|
"""
|
|
785
1215
|
|
|
786
1216
|
request_url = self.users_url() + "/" + user_id + "@" + partition
|
|
787
1217
|
|
|
788
|
-
logger.debug(
|
|
1218
|
+
self.logger.debug(
|
|
789
1219
|
"Get user -> '%s' in partition -> '%s'; calling -> %s",
|
|
790
1220
|
user_id,
|
|
791
1221
|
partition,
|
|
@@ -801,22 +1231,141 @@ class OTDS:
|
|
|
801
1231
|
|
|
802
1232
|
# end method definition
|
|
803
1233
|
|
|
804
|
-
def get_users(
|
|
805
|
-
|
|
1234
|
+
def get_users(
|
|
1235
|
+
self,
|
|
1236
|
+
partition: str = "",
|
|
1237
|
+
where_filter: str | None = None,
|
|
1238
|
+
where_location: str | None = None,
|
|
1239
|
+
where_state: str | None = None,
|
|
1240
|
+
limit: int | None = None,
|
|
1241
|
+
page_size: int | None = None,
|
|
1242
|
+
attributes_as_keys: bool = True,
|
|
1243
|
+
next_page_cookie: str | None = None,
|
|
1244
|
+
) -> dict | None:
|
|
1245
|
+
"""Get all users in a partition. Additional filters can be applied.
|
|
806
1246
|
|
|
807
1247
|
Args:
|
|
808
|
-
partition (str, optional):
|
|
809
|
-
|
|
1248
|
+
partition (str, optional):
|
|
1249
|
+
The name of the partition.
|
|
1250
|
+
where_filter (str | None, optional):
|
|
1251
|
+
Filter returned users. This is a string filter.
|
|
1252
|
+
If None, no filtering applies.
|
|
1253
|
+
where_location (str | None, optional):
|
|
1254
|
+
Filter based on the DN of the Organizational Unit.
|
|
1255
|
+
where_state (str | None, optional):
|
|
1256
|
+
Filter returned users by their state. Possible values are 'enabled' and 'disabled'.
|
|
1257
|
+
If None, no filtering based on state applies.
|
|
1258
|
+
limit (int, optional):
|
|
1259
|
+
The maximum number of users to return. None = unlimited.
|
|
1260
|
+
page_size (int, optional):
|
|
1261
|
+
The chunk size for the number of users returned by one
|
|
1262
|
+
REST API call. If None, then a default of 250 is used.
|
|
1263
|
+
attributes_as_keys (bool, optional):
|
|
1264
|
+
If True, it creates a much simpler to parse result structure
|
|
1265
|
+
per user that includes the user attributes in a "attributes"
|
|
1266
|
+
dictionary where the keys are the attribute names and the
|
|
1267
|
+
values the attribute values.
|
|
1268
|
+
'attributes': {
|
|
1269
|
+
'schemaType' = ['3']
|
|
1270
|
+
'cn' = ['psopentext.com']
|
|
1271
|
+
...
|
|
1272
|
+
}
|
|
1273
|
+
If False a "values" list with "name" and "values" elements is created:
|
|
1274
|
+
'values': [
|
|
1275
|
+
{
|
|
1276
|
+
'name': 'schemaType',
|
|
1277
|
+
'values': ['3']
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
'name': 'cn',
|
|
1281
|
+
'values': ['xyz@opentext.com']
|
|
1282
|
+
},
|
|
1283
|
+
...
|
|
1284
|
+
]
|
|
1285
|
+
Default is True (= attributes as keys).
|
|
1286
|
+
next_page_cookie (str, optional):
|
|
1287
|
+
A key returned by a former call to this method in with
|
|
1288
|
+
a return key 'nextPageCookie' (see example below). This
|
|
1289
|
+
can be used to get the next page of result items.
|
|
1290
|
+
|
|
810
1291
|
Returns:
|
|
811
|
-
dict
|
|
1292
|
+
dict | None:
|
|
1293
|
+
Request response or None if the user was not found.
|
|
1294
|
+
|
|
1295
|
+
Example:
|
|
1296
|
+
{
|
|
1297
|
+
'actualPageSize': 21,
|
|
1298
|
+
'users': [
|
|
1299
|
+
{
|
|
1300
|
+
'userPartitionID': 'Content Server Members',
|
|
1301
|
+
'name': 'ps@opentext.com',
|
|
1302
|
+
'location': 'oTPerson=04f2d12b-b7aa-4797-b4eb-1b6e6bd5ce2e,orgunit=users,partition=Content Server Members,dc=identity,dc=opentext,dc=net',
|
|
1303
|
+
'id': 'ps@opentext.com',
|
|
1304
|
+
'attributes': {
|
|
1305
|
+
'oTExternalID3': ['ps@opentext.com'],
|
|
1306
|
+
'entryUUID': ['04f2d12b-b7aa-4797-b4eb-1b6e6bd5ce2e'],
|
|
1307
|
+
'oTExternalID4': ['Content Server Membersps@opentext.com'],
|
|
1308
|
+
'mail': ['ps@opentext.com'],
|
|
1309
|
+
'displayName': ['Paul Smith'],
|
|
1310
|
+
'oTMemberOf': ['oTGroup=6381fcfe-7b30-4bbb-b849-2cbd8f3a0a48,dc=identity,dc=opentext,dc=net'],
|
|
1311
|
+
'description': ['test description'],
|
|
1312
|
+
'title': ['Lead Systems Analyst'],
|
|
1313
|
+
'oTExternalID1': ['ps@opentext.com'],
|
|
1314
|
+
'modifyTimestamp': ['2025-01-24T10:19:22Z'],
|
|
1315
|
+
'oTExternalID2': ['ps@opentext.com@Content Server Members'],
|
|
1316
|
+
'createTimestamp': ['2025-01-24T10:18:53Z'],
|
|
1317
|
+
'passwordChangedTime': ['2025-01-24T10:18:53Z'],
|
|
1318
|
+
'UserMustChangePasswordAtNextSignIn': ['false'],
|
|
1319
|
+
'sn': ['Smith'],
|
|
1320
|
+
'entryDN': ['oTPerson=04f2d12b-b7aa-4797-b4eb-1b6e6bd5ce2e,orgunit=users,partition=Content Server Members,dc=identity,dc=opentext,dc=net'],
|
|
1321
|
+
'oTObjectGUID': ['BPLRK7eqR5e06xtua9XOLg=='],
|
|
1322
|
+
'UserCannotChangePassword': ['true'],
|
|
1323
|
+
'oTLastLoginTimestamp': ['2025-01-24T10:19:21Z'],
|
|
1324
|
+
'oTSource': ['cs'],
|
|
1325
|
+
'PasswordNeverExpires': ['true'],
|
|
1326
|
+
'givenName': ['Paul'],
|
|
1327
|
+
'cn': ['ps@opentext.com'],
|
|
1328
|
+
'pwdReset': ['true'],
|
|
1329
|
+
'oTObjectIDInResource': ['3b461d9f-ed1d-4be3-859a-316d8eb35aa5:6650'],
|
|
1330
|
+
'accountLockedOut': ['false'],
|
|
1331
|
+
'schemaType': ['3'],
|
|
1332
|
+
'accountDisabled': ['false']
|
|
1333
|
+
}
|
|
1334
|
+
'values': [], # empty because this example is with attrAsKeys = True
|
|
1335
|
+
'customAttributes': None,
|
|
1336
|
+
'objectClass': 'oTPerson',
|
|
1337
|
+
'uuid': '04f2d12b-b7aa-4797-b4eb-1b6e6bd5ce2e',
|
|
1338
|
+
'description': 'test description',
|
|
1339
|
+
'originUUID': None,
|
|
1340
|
+
'urlId': 'xyz@opentext.com',
|
|
1341
|
+
'urlLocation': 'oTPerson=04f2d12b-b7aa-4797-b4eb-1b6e6bd5ce2e,orgunit=users,partition=Content Server Members,dc=identity,dc=opentext,dc=net'
|
|
1342
|
+
},
|
|
1343
|
+
...
|
|
1344
|
+
],
|
|
1345
|
+
'nextPageCookie': 'JIHw2CLHSoeTOmo7Ng/bPw==',
|
|
1346
|
+
'requestedPageSize': 250
|
|
1347
|
+
}
|
|
1348
|
+
|
|
812
1349
|
"""
|
|
813
1350
|
|
|
814
1351
|
# Add query parameters (these are NOT passed via JSon body!)
|
|
815
1352
|
query = {}
|
|
816
|
-
if limit:
|
|
817
|
-
query["limit"] = limit
|
|
818
1353
|
if partition:
|
|
819
1354
|
query["where_partition_name"] = partition
|
|
1355
|
+
if where_filter:
|
|
1356
|
+
query["where_filter"] = where_filter
|
|
1357
|
+
if where_location:
|
|
1358
|
+
query["where_location"] = where_location
|
|
1359
|
+
if where_state:
|
|
1360
|
+
query["where_state"] = where_state
|
|
1361
|
+
if limit:
|
|
1362
|
+
query["limit"] = limit
|
|
1363
|
+
if page_size:
|
|
1364
|
+
query["page_size"] = page_size
|
|
1365
|
+
if attributes_as_keys:
|
|
1366
|
+
query["attrsAsKeys"] = attributes_as_keys
|
|
1367
|
+
if next_page_cookie:
|
|
1368
|
+
query["next_page_cookie"] = next_page_cookie
|
|
820
1369
|
|
|
821
1370
|
encoded_query = urllib.parse.urlencode(query, doseq=True)
|
|
822
1371
|
|
|
@@ -825,17 +1374,17 @@ class OTDS:
|
|
|
825
1374
|
request_url += "?{}".format(encoded_query)
|
|
826
1375
|
|
|
827
1376
|
if partition:
|
|
828
|
-
logger.debug(
|
|
1377
|
+
self.logger.debug(
|
|
829
1378
|
"Get all users in partition -> '%s' (limit -> %s); calling -> %s",
|
|
830
1379
|
partition,
|
|
831
1380
|
limit,
|
|
832
1381
|
request_url,
|
|
833
1382
|
)
|
|
834
1383
|
failure_message = "Failed to get all users in partition -> '{}'".format(
|
|
835
|
-
partition
|
|
1384
|
+
partition,
|
|
836
1385
|
)
|
|
837
1386
|
else:
|
|
838
|
-
logger.debug(
|
|
1387
|
+
self.logger.debug(
|
|
839
1388
|
"Get all users (limit -> %s); calling -> %s",
|
|
840
1389
|
limit,
|
|
841
1390
|
request_url,
|
|
@@ -843,23 +1392,140 @@ class OTDS:
|
|
|
843
1392
|
failure_message = "Failed to get all users"
|
|
844
1393
|
|
|
845
1394
|
return self.do_request(
|
|
846
|
-
url=request_url,
|
|
1395
|
+
url=request_url,
|
|
1396
|
+
method="GET",
|
|
1397
|
+
timeout=None,
|
|
1398
|
+
failure_message=failure_message,
|
|
1399
|
+
)
|
|
1400
|
+
|
|
1401
|
+
# end method definition
|
|
1402
|
+
|
|
1403
|
+
def get_users_iterator(
|
|
1404
|
+
self,
|
|
1405
|
+
partition: str = "",
|
|
1406
|
+
where_state: str | None = None,
|
|
1407
|
+
where_filter: str | None = None,
|
|
1408
|
+
where_location: str | None = None,
|
|
1409
|
+
page_size: int | None = None,
|
|
1410
|
+
) -> iter:
|
|
1411
|
+
"""Get an iterator object that can be used to traverse all members for a given users partition.
|
|
1412
|
+
|
|
1413
|
+
Filters such as user state, location, etc. can be applied.
|
|
1414
|
+
|
|
1415
|
+
Returning a generator avoids loading a large number of nodes into memory at once. Instead you
|
|
1416
|
+
can iterate over the potential large list of related workspaces.
|
|
1417
|
+
|
|
1418
|
+
Example usage:
|
|
1419
|
+
users = otds_object.get_users_iterator(partition="Content Server Members", page_size=10)
|
|
1420
|
+
for user in users:
|
|
1421
|
+
logger.info("Traversing user -> %s", user["name"])
|
|
1422
|
+
|
|
1423
|
+
Args:
|
|
1424
|
+
partition (str, optional):
|
|
1425
|
+
The name of the partition.
|
|
1426
|
+
where_filter (str | None, optional):
|
|
1427
|
+
Filter returned users. This is a string filter.
|
|
1428
|
+
If None, no filtering applies.
|
|
1429
|
+
where_location (str | None, optional):
|
|
1430
|
+
Filter based on the DN of the Organizational Unit.
|
|
1431
|
+
where_state (str | None, optional):
|
|
1432
|
+
Filter returned users by their state. Possible values are 'enabled' and 'disabled'.
|
|
1433
|
+
If None, no filtering based on state applies.
|
|
1434
|
+
page_size (int, optional):
|
|
1435
|
+
The chunk size for the number of users returned by one
|
|
1436
|
+
REST API call. If None, then a default of 250 is used.
|
|
1437
|
+
next_page_cookie (str, optional):
|
|
1438
|
+
A key returned by a former call to this method in with
|
|
1439
|
+
a return key 'nextPageCookie' (see example below). This
|
|
1440
|
+
can be used to get the next page of result items.
|
|
1441
|
+
|
|
1442
|
+
Returns:
|
|
1443
|
+
iter:
|
|
1444
|
+
A generator yielding one OTDS user per iteration.
|
|
1445
|
+
If the REST API fails, returns no value.
|
|
1446
|
+
|
|
1447
|
+
"""
|
|
1448
|
+
|
|
1449
|
+
next_page_cookie = None
|
|
1450
|
+
|
|
1451
|
+
while True:
|
|
1452
|
+
response = self.get_users(
|
|
1453
|
+
partition=partition,
|
|
1454
|
+
where_filter=where_filter,
|
|
1455
|
+
where_location=where_location,
|
|
1456
|
+
where_state=where_state,
|
|
1457
|
+
page_size=page_size,
|
|
1458
|
+
next_page_cookie=next_page_cookie,
|
|
1459
|
+
)
|
|
1460
|
+
if not response or "users" not in response:
|
|
1461
|
+
# Don't return None! Plain return is what we need for iterators.
|
|
1462
|
+
# Natural Termination: If the generator does not yield, it behaves
|
|
1463
|
+
# like an empty iterable when used in a loop or converted to a list:
|
|
1464
|
+
return
|
|
1465
|
+
|
|
1466
|
+
# Yield users one at a time:
|
|
1467
|
+
yield from response["users"]
|
|
1468
|
+
|
|
1469
|
+
# See if we have an additional result page.
|
|
1470
|
+
# If not terminate the iterator and return
|
|
1471
|
+
# no value.
|
|
1472
|
+
next_page_cookie = response["nextPageCookie"]
|
|
1473
|
+
if not next_page_cookie:
|
|
1474
|
+
# Don't return None! Plain return is what we need for iterators.
|
|
1475
|
+
# Natural Termination: If the generator does not yield, it behaves
|
|
1476
|
+
# like an empty iterable when used in a loop or converted to a list:
|
|
1477
|
+
return
|
|
1478
|
+
|
|
1479
|
+
# end method definition
|
|
1480
|
+
|
|
1481
|
+
def get_current_user(self) -> dict | None:
|
|
1482
|
+
"""Get the currently logged in user.
|
|
1483
|
+
|
|
1484
|
+
Returns:
|
|
1485
|
+
dict | None:
|
|
1486
|
+
Request response or None if the user was not found.
|
|
1487
|
+
|
|
1488
|
+
"""
|
|
1489
|
+
|
|
1490
|
+
request_url = self.current_user_url()
|
|
1491
|
+
|
|
1492
|
+
self.logger.debug(
|
|
1493
|
+
"Get current user; calling -> %s",
|
|
1494
|
+
request_url,
|
|
1495
|
+
)
|
|
1496
|
+
|
|
1497
|
+
return self.do_request(
|
|
1498
|
+
url=request_url,
|
|
1499
|
+
method="GET",
|
|
1500
|
+
timeout=None,
|
|
1501
|
+
failure_message="Failed to get current user",
|
|
847
1502
|
)
|
|
848
1503
|
|
|
849
1504
|
# end method definition
|
|
850
1505
|
|
|
851
1506
|
def update_user(
|
|
852
|
-
self,
|
|
1507
|
+
self,
|
|
1508
|
+
partition: str,
|
|
1509
|
+
user_id: str,
|
|
1510
|
+
attribute_name: str,
|
|
1511
|
+
attribute_value: str,
|
|
853
1512
|
) -> dict | None:
|
|
854
|
-
"""Update a user attribute with a new value
|
|
1513
|
+
"""Update a user attribute with a new value.
|
|
855
1514
|
|
|
856
1515
|
Args:
|
|
857
|
-
partition (str):
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1516
|
+
partition (str):
|
|
1517
|
+
The name of the partition the user is in.
|
|
1518
|
+
user_id (str):
|
|
1519
|
+
The ID of the user (= login name).
|
|
1520
|
+
attribute_name (str):
|
|
1521
|
+
The name of the attribute.
|
|
1522
|
+
attribute_value (str):
|
|
1523
|
+
The new (updated) value of the attribute.
|
|
1524
|
+
|
|
1525
|
+
Returns:
|
|
1526
|
+
dict | None:
|
|
1527
|
+
Request response or None if the update fails.
|
|
1528
|
+
|
|
863
1529
|
"""
|
|
864
1530
|
|
|
865
1531
|
if attribute_name in ["description"]:
|
|
@@ -875,7 +1541,7 @@ class OTDS:
|
|
|
875
1541
|
|
|
876
1542
|
request_url = self.users_url() + "/" + user_id
|
|
877
1543
|
|
|
878
|
-
logger.debug(
|
|
1544
|
+
self.logger.debug(
|
|
879
1545
|
"Update user -> '%s' attribute -> '%s' to value -> '%s'; calling -> %s",
|
|
880
1546
|
user_id,
|
|
881
1547
|
attribute_name,
|
|
@@ -894,18 +1560,23 @@ class OTDS:
|
|
|
894
1560
|
# end method definition
|
|
895
1561
|
|
|
896
1562
|
def delete_user(self, partition: str, user_id: str) -> bool:
|
|
897
|
-
"""Delete an existing user
|
|
1563
|
+
"""Delete an existing user.
|
|
898
1564
|
|
|
899
1565
|
Args:
|
|
900
|
-
partition (str):
|
|
901
|
-
|
|
1566
|
+
partition (str):
|
|
1567
|
+
The name of the partition the user is in.
|
|
1568
|
+
user_id (str):
|
|
1569
|
+
The ID (= login name) of the user to delete.
|
|
1570
|
+
|
|
902
1571
|
Returns:
|
|
903
|
-
bool:
|
|
1572
|
+
bool:
|
|
1573
|
+
True = success, False = error
|
|
1574
|
+
|
|
904
1575
|
"""
|
|
905
1576
|
|
|
906
1577
|
request_url = self.users_url() + "/" + user_id + "@" + partition
|
|
907
1578
|
|
|
908
|
-
logger.debug(
|
|
1579
|
+
self.logger.debug(
|
|
909
1580
|
"Delete user -> '%s' in partition -> '%s'; calling -> %s",
|
|
910
1581
|
user_id,
|
|
911
1582
|
partition,
|
|
@@ -920,29 +1591,33 @@ class OTDS:
|
|
|
920
1591
|
parse_request_response=False,
|
|
921
1592
|
)
|
|
922
1593
|
|
|
923
|
-
|
|
924
|
-
return True
|
|
925
|
-
|
|
926
|
-
return False
|
|
1594
|
+
bool(response and response.ok)
|
|
927
1595
|
|
|
928
1596
|
# end method definition
|
|
929
1597
|
|
|
930
1598
|
def reset_user_password(self, user_id: str, password: str) -> bool:
|
|
931
|
-
"""Reset a password of an existing user
|
|
1599
|
+
"""Reset a password of an existing user.
|
|
932
1600
|
|
|
933
1601
|
Args:
|
|
934
|
-
user_id (str):
|
|
935
|
-
|
|
1602
|
+
user_id (str):
|
|
1603
|
+
The Id (= login name) of the user.
|
|
1604
|
+
password (str):
|
|
1605
|
+
The new password of the user.
|
|
1606
|
+
|
|
936
1607
|
Returns:
|
|
937
|
-
bool:
|
|
1608
|
+
bool:
|
|
1609
|
+
True = success, False = error.
|
|
1610
|
+
|
|
938
1611
|
"""
|
|
939
1612
|
|
|
940
1613
|
user_post_body_json = {"newPassword": password}
|
|
941
1614
|
|
|
942
1615
|
request_url = "{}/{}/password".format(self.users_url(), user_id)
|
|
943
1616
|
|
|
944
|
-
logger.debug(
|
|
945
|
-
"Resetting password for user -> '%s'; calling -> %s",
|
|
1617
|
+
self.logger.debug(
|
|
1618
|
+
"Resetting password for user -> '%s'; calling -> %s",
|
|
1619
|
+
user_id,
|
|
1620
|
+
request_url,
|
|
946
1621
|
)
|
|
947
1622
|
|
|
948
1623
|
response = self.do_request(
|
|
@@ -954,22 +1629,25 @@ class OTDS:
|
|
|
954
1629
|
parse_request_response=False,
|
|
955
1630
|
)
|
|
956
1631
|
|
|
957
|
-
|
|
958
|
-
return True
|
|
959
|
-
|
|
960
|
-
return False
|
|
1632
|
+
bool(response and response.ok)
|
|
961
1633
|
|
|
962
1634
|
# end method definition
|
|
963
1635
|
|
|
964
1636
|
def add_group(self, partition: str, name: str, description: str) -> dict | None:
|
|
965
|
-
"""Add a new user group to a user partition in OTDS
|
|
1637
|
+
"""Add a new user group to a user partition in OTDS.
|
|
966
1638
|
|
|
967
1639
|
Args:
|
|
968
|
-
partition (str):
|
|
969
|
-
|
|
970
|
-
|
|
1640
|
+
partition (str):
|
|
1641
|
+
The name of the OTDS user partition (needs to exist).
|
|
1642
|
+
name (str):
|
|
1643
|
+
The name of the new group.
|
|
1644
|
+
description (str):
|
|
1645
|
+
The description of the new group.
|
|
1646
|
+
|
|
971
1647
|
Returns:
|
|
972
|
-
dict
|
|
1648
|
+
dict | None:
|
|
1649
|
+
Request response (json) or None if the creation fails.
|
|
1650
|
+
|
|
973
1651
|
"""
|
|
974
1652
|
|
|
975
1653
|
group_post_body_json = {
|
|
@@ -980,13 +1658,13 @@ class OTDS:
|
|
|
980
1658
|
|
|
981
1659
|
request_url = self.groups_url()
|
|
982
1660
|
|
|
983
|
-
logger.debug(
|
|
1661
|
+
self.logger.debug(
|
|
984
1662
|
"Adding group -> '%s' to partition -> '%s'; calling -> %s",
|
|
985
1663
|
name,
|
|
986
1664
|
partition,
|
|
987
1665
|
request_url,
|
|
988
1666
|
)
|
|
989
|
-
logger.debug("Group Attributes -> %s", str(group_post_body_json))
|
|
1667
|
+
self.logger.debug("Group Attributes -> %s", str(group_post_body_json))
|
|
990
1668
|
|
|
991
1669
|
return self.do_request(
|
|
992
1670
|
url=request_url,
|
|
@@ -999,14 +1677,19 @@ class OTDS:
|
|
|
999
1677
|
# end method definition
|
|
1000
1678
|
|
|
1001
1679
|
def get_group(self, group: str, show_error: bool = True) -> dict | None:
|
|
1002
|
-
"""Get a OTDS group by its group name
|
|
1680
|
+
"""Get a OTDS group by its group name.
|
|
1003
1681
|
|
|
1004
1682
|
Args:
|
|
1005
|
-
group (str):
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1683
|
+
group (str):
|
|
1684
|
+
The ID of the group (= group name).
|
|
1685
|
+
show_error (bool, optional):
|
|
1686
|
+
If True, log an error if resource is not found. Otherwise log a warning.
|
|
1687
|
+
|
|
1688
|
+
Returns:
|
|
1689
|
+
dict | None:
|
|
1690
|
+
Request response or None if the group was not found.
|
|
1691
|
+
|
|
1692
|
+
Example:
|
|
1010
1693
|
{
|
|
1011
1694
|
'numMembers': 7,
|
|
1012
1695
|
'userPartitionID': 'Content Server Members',
|
|
@@ -1022,11 +1705,12 @@ class OTDS:
|
|
|
1022
1705
|
'urlId': 'Sales@Content Server Members',
|
|
1023
1706
|
'urlLocation': 'oTGroup=3f921294-b92a-4c9e-bf7c-b50df16bb937,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net'
|
|
1024
1707
|
}
|
|
1708
|
+
|
|
1025
1709
|
"""
|
|
1026
1710
|
|
|
1027
1711
|
request_url = self.groups_url() + "/" + group
|
|
1028
1712
|
|
|
1029
|
-
logger.debug("Get group -> '%s'; calling -> %s", group, request_url)
|
|
1713
|
+
self.logger.debug("Get group -> '%s'; calling -> %s", group, request_url)
|
|
1030
1714
|
|
|
1031
1715
|
return self.do_request(
|
|
1032
1716
|
url=request_url,
|
|
@@ -1038,21 +1722,275 @@ class OTDS:
|
|
|
1038
1722
|
|
|
1039
1723
|
# end method definition
|
|
1040
1724
|
|
|
1725
|
+
def get_groups(
|
|
1726
|
+
self,
|
|
1727
|
+
partition: str = "",
|
|
1728
|
+
where_filter: str | None = None,
|
|
1729
|
+
where_location: str | None = None,
|
|
1730
|
+
limit: int | None = None,
|
|
1731
|
+
page_size: int | None = None,
|
|
1732
|
+
attributes_as_keys: bool = True,
|
|
1733
|
+
next_page_cookie: str | None = None,
|
|
1734
|
+
) -> dict | None:
|
|
1735
|
+
"""Get all groups in a partition. Additional filters can be applied.
|
|
1736
|
+
|
|
1737
|
+
Args:
|
|
1738
|
+
partition (str, optional):
|
|
1739
|
+
The name of the partition.
|
|
1740
|
+
where_filter (str | None, optional):
|
|
1741
|
+
Filter returned groups. This is a string filter.
|
|
1742
|
+
If None, no filtering applies.
|
|
1743
|
+
where_location (str | None, optional):
|
|
1744
|
+
Filter based on the DN of the Organizational Unit.
|
|
1745
|
+
limit (int, optional):
|
|
1746
|
+
The maximum number of groups to return. None = unlimited.
|
|
1747
|
+
page_size (int, optional):
|
|
1748
|
+
The chunk size for the number of groups returned by one
|
|
1749
|
+
REST API call. If None, then a default of 250 is used.
|
|
1750
|
+
attributes_as_keys (bool, optional):
|
|
1751
|
+
If True, it creates a much simpler to parse result structure
|
|
1752
|
+
per group that includes the group attributes in a "attributes"
|
|
1753
|
+
dictionary where the keys are the attribute names and the
|
|
1754
|
+
values the attribute values.
|
|
1755
|
+
'attributes': {
|
|
1756
|
+
'schemaType' = ['3']
|
|
1757
|
+
'cn' = [...]
|
|
1758
|
+
...
|
|
1759
|
+
}
|
|
1760
|
+
If False a "values" list with "name" and "values" elements is created:
|
|
1761
|
+
'values': [
|
|
1762
|
+
{
|
|
1763
|
+
'name': 'schemaType',
|
|
1764
|
+
'values': ['3']
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
'name': 'cn',
|
|
1768
|
+
'values': ['...']
|
|
1769
|
+
},
|
|
1770
|
+
...
|
|
1771
|
+
]
|
|
1772
|
+
Default is True (= attributes as keys).
|
|
1773
|
+
next_page_cookie (str, optional):
|
|
1774
|
+
A key returned by a former call to this method in with
|
|
1775
|
+
a return key 'nextPageCookie' (see example below). This
|
|
1776
|
+
can be used to get the next page of result items.
|
|
1777
|
+
|
|
1778
|
+
Returns:
|
|
1779
|
+
dict | None:
|
|
1780
|
+
Request response or None if the user was not found.
|
|
1781
|
+
|
|
1782
|
+
Example:
|
|
1783
|
+
{
|
|
1784
|
+
'groups': [
|
|
1785
|
+
{
|
|
1786
|
+
'numMembers': 0,
|
|
1787
|
+
'userPartitionID': 'Content Server Members',
|
|
1788
|
+
'name': 'Unified_ArchiveLink',
|
|
1789
|
+
'location': 'oTGroup=050a3c27-7636-4406-a94e-dcc4947fa21f,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net',
|
|
1790
|
+
'id': 'Unified_ArchiveLink@Content Server Members',
|
|
1791
|
+
'attributes': {
|
|
1792
|
+
'oTExternalID3': [...],
|
|
1793
|
+
'entryUUID': [...],
|
|
1794
|
+
'oTExternalID4': [...],
|
|
1795
|
+
'oTObjectIDInResource': [...],
|
|
1796
|
+
'oTSource': [...],
|
|
1797
|
+
'schemaType': [...],
|
|
1798
|
+
'cn': [...],
|
|
1799
|
+
'oTObjectGUID': [...],
|
|
1800
|
+
'oTExternalID1': [...],
|
|
1801
|
+
'entryDN': [...],
|
|
1802
|
+
'oTExternalID2': [...],
|
|
1803
|
+
'createTimestamp': [...]
|
|
1804
|
+
},
|
|
1805
|
+
'values': None,
|
|
1806
|
+
'customAttributes': None,
|
|
1807
|
+
'objectClass': 'oTGroup',
|
|
1808
|
+
'uuid': '050a3c27-7636-4406-a94e-dcc4947fa21f',
|
|
1809
|
+
'description': None,
|
|
1810
|
+
'originUUID': None,
|
|
1811
|
+
'urlId': 'Unified_ArchiveLink@Content Server Members',
|
|
1812
|
+
'urlLocation': 'oTGroup=050a3c27-7636-4406-a94e-dcc4947fa21f,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net'
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
'numMembers': 0,
|
|
1816
|
+
'userPartitionID': 'Content Server Members',
|
|
1817
|
+
'name': 'R&D',
|
|
1818
|
+
'location': 'oTGroup=24356f83-5636-47f0-9ac3-9646d3b34804,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net',
|
|
1819
|
+
'id': 'R&D@Content Server Members',
|
|
1820
|
+
'attributes': {
|
|
1821
|
+
'oTExternalID3': [...],
|
|
1822
|
+
'entryUUID': [...],
|
|
1823
|
+
'oTExternalID4': [...],
|
|
1824
|
+
'oTObjectIDInResource': [...],
|
|
1825
|
+
'oTSource': [...],
|
|
1826
|
+
'schemaType': [...],
|
|
1827
|
+
'cn': [...],
|
|
1828
|
+
'oTObjectGUID': [...],
|
|
1829
|
+
'oTExternalID1': [...],
|
|
1830
|
+
'entryDN': [...],
|
|
1831
|
+
'oTExternalID2': [...],
|
|
1832
|
+
'createTimestamp': [...]
|
|
1833
|
+
},
|
|
1834
|
+
'values': None,
|
|
1835
|
+
'customAttributes': None,
|
|
1836
|
+
'objectClass': 'oTGroup',
|
|
1837
|
+
'uuid': '24356f83-5636-47f0-9ac3-9646d3b34804',
|
|
1838
|
+
'description': None,
|
|
1839
|
+
'originUUID': None,
|
|
1840
|
+
'urlId': 'R&D@Content Server Members',
|
|
1841
|
+
'urlLocation': 'oTGroup=24356f83-5636-47f0-9ac3-9646d3b34804,orgunit=groups,partition=Content Server Members,dc=identity,dc=opentext,dc=net'
|
|
1842
|
+
},
|
|
1843
|
+
...
|
|
1844
|
+
],
|
|
1845
|
+
'actualPageSize': 5,
|
|
1846
|
+
'nextPageCookie': 'JIHw2CLHSoeTOmo7Ng/bPw==',
|
|
1847
|
+
'requestedPageSize': 5
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
"""
|
|
1851
|
+
|
|
1852
|
+
# Add query parameters (these are NOT passed via request body!)
|
|
1853
|
+
query = {}
|
|
1854
|
+
if partition:
|
|
1855
|
+
query["where_partition_name"] = partition
|
|
1856
|
+
if where_filter:
|
|
1857
|
+
query["where_filter"] = where_filter
|
|
1858
|
+
if where_location:
|
|
1859
|
+
query["where_location"] = where_location
|
|
1860
|
+
if limit:
|
|
1861
|
+
query["limit"] = limit
|
|
1862
|
+
if page_size:
|
|
1863
|
+
query["page_size"] = page_size
|
|
1864
|
+
if attributes_as_keys:
|
|
1865
|
+
query["attrsAsKeys"] = attributes_as_keys
|
|
1866
|
+
if next_page_cookie:
|
|
1867
|
+
query["next_page_cookie"] = next_page_cookie
|
|
1868
|
+
|
|
1869
|
+
encoded_query = urllib.parse.urlencode(query, doseq=True)
|
|
1870
|
+
|
|
1871
|
+
request_url = self.groups_url()
|
|
1872
|
+
if query:
|
|
1873
|
+
request_url += "?{}".format(encoded_query)
|
|
1874
|
+
|
|
1875
|
+
if partition:
|
|
1876
|
+
self.logger.debug(
|
|
1877
|
+
"Get all groups in partition -> '%s' (limit -> %s, page size -> %s); calling -> %s",
|
|
1878
|
+
partition,
|
|
1879
|
+
str(limit),
|
|
1880
|
+
str(page_size),
|
|
1881
|
+
request_url,
|
|
1882
|
+
)
|
|
1883
|
+
failure_message = "Failed to get all groups in partition -> '{}'".format(
|
|
1884
|
+
partition,
|
|
1885
|
+
)
|
|
1886
|
+
else:
|
|
1887
|
+
self.logger.debug(
|
|
1888
|
+
"Get all groups (limit -> %s); calling -> %s",
|
|
1889
|
+
limit,
|
|
1890
|
+
request_url,
|
|
1891
|
+
)
|
|
1892
|
+
failure_message = "Failed to get all groups"
|
|
1893
|
+
|
|
1894
|
+
return self.do_request(
|
|
1895
|
+
url=request_url,
|
|
1896
|
+
method="GET",
|
|
1897
|
+
timeout=None,
|
|
1898
|
+
failure_message=failure_message,
|
|
1899
|
+
)
|
|
1900
|
+
|
|
1901
|
+
# end method definition
|
|
1902
|
+
|
|
1903
|
+
def get_groups_iterator(
|
|
1904
|
+
self,
|
|
1905
|
+
partition: str = "",
|
|
1906
|
+
where_filter: str | None = None,
|
|
1907
|
+
where_location: str | None = None,
|
|
1908
|
+
page_size: int | None = None,
|
|
1909
|
+
) -> iter:
|
|
1910
|
+
"""Get an iterator object that can be used to traverse all groups for a given users partition.
|
|
1911
|
+
|
|
1912
|
+
Returning a generator avoids loading a large number of nodes into memory at once. Instead you
|
|
1913
|
+
can iterate over the potential large list of related workspaces.
|
|
1914
|
+
|
|
1915
|
+
Example usage:
|
|
1916
|
+
groups = otds_object.get_groups_iterator(partition="Content Server Members", page_size=10)
|
|
1917
|
+
for group in groups:
|
|
1918
|
+
logger.info("Traversing group -> %s", group["name"])
|
|
1919
|
+
|
|
1920
|
+
Args:
|
|
1921
|
+
partition (str, optional):
|
|
1922
|
+
The name of the partition.
|
|
1923
|
+
where_filter (str | None, optional):
|
|
1924
|
+
Filter returned groups. This is a string filter.
|
|
1925
|
+
If None, no filtering applies.
|
|
1926
|
+
where_location (str | None, optional):
|
|
1927
|
+
Filter based on the DN of the Organizational Unit.
|
|
1928
|
+
page_size (int, optional):
|
|
1929
|
+
The chunk size for the number of groups returned by one
|
|
1930
|
+
REST API call. If None, then a default of 250 is used.
|
|
1931
|
+
next_page_cookie (str, optional):
|
|
1932
|
+
A key returned by a former call to this method in with
|
|
1933
|
+
a return key 'nextPageCookie' (see example below). This
|
|
1934
|
+
can be used to get the next page of result items.
|
|
1935
|
+
|
|
1936
|
+
Returns:
|
|
1937
|
+
iter:
|
|
1938
|
+
A generator yielding one OTDS group per iteration.
|
|
1939
|
+
If the REST API fails, returns no value.
|
|
1940
|
+
|
|
1941
|
+
"""
|
|
1942
|
+
|
|
1943
|
+
next_page_cookie = None
|
|
1944
|
+
|
|
1945
|
+
while True:
|
|
1946
|
+
response = self.get_groups(
|
|
1947
|
+
partition=partition,
|
|
1948
|
+
where_filter=where_filter,
|
|
1949
|
+
where_location=where_location,
|
|
1950
|
+
page_size=page_size,
|
|
1951
|
+
next_page_cookie=next_page_cookie,
|
|
1952
|
+
)
|
|
1953
|
+
if not response or "groups" not in response:
|
|
1954
|
+
# Don't return None! Plain return is what we need for iterators.
|
|
1955
|
+
# Natural Termination: If the generator does not yield, it behaves
|
|
1956
|
+
# like an empty iterable when used in a loop or converted to a list:
|
|
1957
|
+
return
|
|
1958
|
+
|
|
1959
|
+
# Yield users one at a time:
|
|
1960
|
+
yield from response["groups"]
|
|
1961
|
+
|
|
1962
|
+
# See if we have an additional result page.
|
|
1963
|
+
# If not terminate the iterator and return
|
|
1964
|
+
# no value.
|
|
1965
|
+
next_page_cookie = response["nextPageCookie"]
|
|
1966
|
+
if not next_page_cookie:
|
|
1967
|
+
# Don't return None! Plain return is what we need for iterators.
|
|
1968
|
+
# Natural Termination: If the generator does not yield, it behaves
|
|
1969
|
+
# like an empty iterable when used in a loop or converted to a list:
|
|
1970
|
+
return
|
|
1971
|
+
|
|
1972
|
+
# end method definition
|
|
1973
|
+
|
|
1041
1974
|
def add_user_to_group(self, user: str, group: str) -> bool:
|
|
1042
|
-
"""Add an existing user to an existing group in OTDS
|
|
1975
|
+
"""Add an existing user to an existing group in OTDS.
|
|
1043
1976
|
|
|
1044
1977
|
Args:
|
|
1045
|
-
user (str):
|
|
1046
|
-
|
|
1978
|
+
user (str):
|
|
1979
|
+
The name of the OTDS user (needs to exist).
|
|
1980
|
+
group (str):
|
|
1981
|
+
The name of the OTDS group (needs to exist).
|
|
1982
|
+
|
|
1047
1983
|
Returns:
|
|
1048
|
-
bool:
|
|
1984
|
+
bool:
|
|
1985
|
+
True, if the request is successful, False otherwise.
|
|
1986
|
+
|
|
1049
1987
|
"""
|
|
1050
1988
|
|
|
1051
1989
|
user_to_group_post_body_json = {"stringList": [group]}
|
|
1052
1990
|
|
|
1053
1991
|
request_url = self.users_url() + "/" + user + "/memberof"
|
|
1054
1992
|
|
|
1055
|
-
logger.debug(
|
|
1993
|
+
self.logger.debug(
|
|
1056
1994
|
"Adding user -> '%s' to group -> '%s'; calling -> %s",
|
|
1057
1995
|
user,
|
|
1058
1996
|
group,
|
|
@@ -1066,33 +2004,36 @@ class OTDS:
|
|
|
1066
2004
|
json_data=user_to_group_post_body_json,
|
|
1067
2005
|
timeout=None,
|
|
1068
2006
|
failure_message="Failed to add user -> '{}' to group -> '{}'".format(
|
|
1069
|
-
user,
|
|
2007
|
+
user,
|
|
2008
|
+
group,
|
|
1070
2009
|
),
|
|
1071
2010
|
parse_request_response=False,
|
|
1072
2011
|
)
|
|
1073
2012
|
|
|
1074
|
-
|
|
1075
|
-
return True
|
|
1076
|
-
|
|
1077
|
-
return False
|
|
2013
|
+
return bool(response and response.ok)
|
|
1078
2014
|
|
|
1079
2015
|
# end method definition
|
|
1080
2016
|
|
|
1081
2017
|
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
|
|
2018
|
+
"""Add an existing group to an existing parent group in OTDS.
|
|
1083
2019
|
|
|
1084
2020
|
Args:
|
|
1085
|
-
group (str):
|
|
1086
|
-
|
|
2021
|
+
group (str):
|
|
2022
|
+
The name of the OTDS group (needs to exist).
|
|
2023
|
+
parent_group (str):
|
|
2024
|
+
The name of the OTDS parent group (needs to exist).
|
|
2025
|
+
|
|
1087
2026
|
Returns:
|
|
1088
|
-
bool:
|
|
2027
|
+
bool:
|
|
2028
|
+
True, if the request is successful, False otherwise.
|
|
2029
|
+
|
|
1089
2030
|
"""
|
|
1090
2031
|
|
|
1091
2032
|
group_to_parent_group_post_body_json = {"stringList": [parent_group]}
|
|
1092
2033
|
|
|
1093
2034
|
request_url = self.groups_url() + "/" + group + "/memberof"
|
|
1094
2035
|
|
|
1095
|
-
logger.debug(
|
|
2036
|
+
self.logger.debug(
|
|
1096
2037
|
"Adding group -> '%s' to parent group -> '%s'; calling -> %s",
|
|
1097
2038
|
group,
|
|
1098
2039
|
parent_group,
|
|
@@ -1106,15 +2047,13 @@ class OTDS:
|
|
|
1106
2047
|
json_data=group_to_parent_group_post_body_json,
|
|
1107
2048
|
timeout=None,
|
|
1108
2049
|
failure_message="Failed to add group -> '{}' to parent group -> '{}'".format(
|
|
1109
|
-
group,
|
|
2050
|
+
group,
|
|
2051
|
+
parent_group,
|
|
1110
2052
|
),
|
|
1111
2053
|
parse_request_response=False,
|
|
1112
2054
|
)
|
|
1113
2055
|
|
|
1114
|
-
|
|
1115
|
-
return True
|
|
1116
|
-
|
|
1117
|
-
return False
|
|
2056
|
+
return bool(response and response.ok)
|
|
1118
2057
|
|
|
1119
2058
|
# end method definition
|
|
1120
2059
|
|
|
@@ -1128,15 +2067,29 @@ class OTDS:
|
|
|
1128
2067
|
secret: str | None = None, # needs to be 16 bytes!
|
|
1129
2068
|
additional_payload: dict | None = None,
|
|
1130
2069
|
) -> dict | None:
|
|
1131
|
-
"""Add an OTDS resource
|
|
2070
|
+
"""Add an OTDS resource.
|
|
1132
2071
|
|
|
1133
2072
|
Args:
|
|
1134
|
-
name (str):
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
2073
|
+
name (str):
|
|
2074
|
+
The name of the new OTDS resource.
|
|
2075
|
+
description (str):
|
|
2076
|
+
The optional description of the new OTDS resource.
|
|
2077
|
+
display_name (str, optional):
|
|
2078
|
+
The optional display name of the OTDS resource.
|
|
2079
|
+
allow_impersonation (bool):
|
|
2080
|
+
Defines whether or not the resource allows impersonation.
|
|
2081
|
+
resource_id (str | None, optional):
|
|
2082
|
+
Allows to set a predefined resource ID. This requires the
|
|
2083
|
+
secret parameter in additon.
|
|
2084
|
+
secret (str):
|
|
2085
|
+
A 24 charcters secret key. Required to set a predefined resource ID.
|
|
2086
|
+
additional_payload (dict, optional):
|
|
2087
|
+
Additional values for the JSON payload.
|
|
2088
|
+
|
|
1138
2089
|
Returns:
|
|
1139
|
-
dict
|
|
2090
|
+
dict | None:
|
|
2091
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2092
|
+
|
|
1140
2093
|
"""
|
|
1141
2094
|
|
|
1142
2095
|
resource_post_body = {
|
|
@@ -1147,8 +2100,8 @@ class OTDS:
|
|
|
1147
2100
|
}
|
|
1148
2101
|
|
|
1149
2102
|
if resource_id and not secret:
|
|
1150
|
-
logger.error(
|
|
1151
|
-
"A resource ID can only be specified if a secret value is also provided!"
|
|
2103
|
+
self.logger.error(
|
|
2104
|
+
"A resource ID can only be specified if a secret value is also provided!",
|
|
1152
2105
|
)
|
|
1153
2106
|
return None
|
|
1154
2107
|
|
|
@@ -1156,8 +2109,8 @@ class OTDS:
|
|
|
1156
2109
|
resource_post_body["resourceID"] = resource_id
|
|
1157
2110
|
if secret:
|
|
1158
2111
|
if len(secret) != 24 or not secret.endswith("=="):
|
|
1159
|
-
logger.warning(
|
|
1160
|
-
"The secret should by 24 characters long and should end with '=='"
|
|
2112
|
+
self.logger.warning(
|
|
2113
|
+
"The secret should by 24 characters long and should end with '=='",
|
|
1161
2114
|
)
|
|
1162
2115
|
resource_post_body["secretKey"] = secret
|
|
1163
2116
|
|
|
@@ -1168,7 +2121,7 @@ class OTDS:
|
|
|
1168
2121
|
|
|
1169
2122
|
request_url = self.config()["resourceUrl"]
|
|
1170
2123
|
|
|
1171
|
-
logger.debug(
|
|
2124
|
+
self.logger.debug(
|
|
1172
2125
|
"Adding resource -> '%s' ('%s'); calling -> %s",
|
|
1173
2126
|
name,
|
|
1174
2127
|
description,
|
|
@@ -1186,15 +2139,19 @@ class OTDS:
|
|
|
1186
2139
|
# end method definition
|
|
1187
2140
|
|
|
1188
2141
|
def get_resource(self, name: str, show_error: bool = False) -> dict | None:
|
|
1189
|
-
"""Get an existing OTDS resource
|
|
2142
|
+
"""Get an existing OTDS resource.
|
|
1190
2143
|
|
|
1191
2144
|
Args:
|
|
1192
|
-
name (str):
|
|
1193
|
-
|
|
2145
|
+
name (str):
|
|
2146
|
+
The name of the new OTDS resource.
|
|
2147
|
+
show_error (bool, optional):
|
|
2148
|
+
If True, log an error if resource is not found. Else log just a warning.
|
|
2149
|
+
|
|
1194
2150
|
Returns:
|
|
1195
|
-
dict
|
|
2151
|
+
dict | None:
|
|
2152
|
+
Request response or None if the REST call fails.
|
|
1196
2153
|
|
|
1197
|
-
|
|
2154
|
+
Example:
|
|
1198
2155
|
{
|
|
1199
2156
|
'resourceName': 'cs',
|
|
1200
2157
|
'id': 'cs',
|
|
@@ -1223,11 +2180,12 @@ class OTDS:
|
|
|
1223
2180
|
'logonStyle': None,
|
|
1224
2181
|
'logonUXVersion': 0
|
|
1225
2182
|
}
|
|
2183
|
+
|
|
1226
2184
|
"""
|
|
1227
2185
|
|
|
1228
2186
|
request_url = "{}/{}".format(self.config()["resourceUrl"], name)
|
|
1229
2187
|
|
|
1230
|
-
logger.debug("Get resource -> '%s'; calling -> %s", name, request_url)
|
|
2188
|
+
self.logger.debug("Get resource -> '%s'; calling -> %s", name, request_url)
|
|
1231
2189
|
|
|
1232
2190
|
return self.do_request(
|
|
1233
2191
|
url=request_url,
|
|
@@ -1240,21 +2198,30 @@ class OTDS:
|
|
|
1240
2198
|
# end method definition
|
|
1241
2199
|
|
|
1242
2200
|
def update_resource(
|
|
1243
|
-
self,
|
|
2201
|
+
self,
|
|
2202
|
+
name: str,
|
|
2203
|
+
resource: object,
|
|
2204
|
+
show_error: bool = True,
|
|
1244
2205
|
) -> dict | None:
|
|
1245
|
-
"""Update an existing OTDS resource
|
|
2206
|
+
"""Update an existing OTDS resource.
|
|
1246
2207
|
|
|
1247
2208
|
Args:
|
|
1248
|
-
name (str):
|
|
1249
|
-
|
|
1250
|
-
|
|
2209
|
+
name (str):
|
|
2210
|
+
The name of the OTDS resource to update.
|
|
2211
|
+
resource (object):
|
|
2212
|
+
updated resource object of get_resource called before
|
|
2213
|
+
show_error (bool, optional):
|
|
2214
|
+
If True, log an error if resource is not found. Else just log a warning.
|
|
2215
|
+
|
|
1251
2216
|
Returns:
|
|
1252
|
-
dict
|
|
2217
|
+
dict | None:
|
|
2218
|
+
Request response (json) or None if the REST call fails.
|
|
2219
|
+
|
|
1253
2220
|
"""
|
|
1254
2221
|
|
|
1255
2222
|
request_url = "{}/{}".format(self.config()["resourceUrl"], name)
|
|
1256
2223
|
|
|
1257
|
-
logger.debug("Updating resource -> '%s'; calling -> %s", name, request_url)
|
|
2224
|
+
self.logger.debug("Updating resource -> '%s'; calling -> %s", name, request_url)
|
|
1258
2225
|
|
|
1259
2226
|
return self.do_request(
|
|
1260
2227
|
url=request_url,
|
|
@@ -1268,20 +2235,26 @@ class OTDS:
|
|
|
1268
2235
|
# end method definition
|
|
1269
2236
|
|
|
1270
2237
|
def activate_resource(self, resource_id: str) -> dict | None:
|
|
1271
|
-
"""Activate an OTDS resource
|
|
2238
|
+
"""Activate an OTDS resource.
|
|
1272
2239
|
|
|
1273
2240
|
Args:
|
|
1274
|
-
resource_id (str):
|
|
2241
|
+
resource_id (str):
|
|
2242
|
+
The ID of the OTDS resource to update.
|
|
2243
|
+
|
|
1275
2244
|
Returns:
|
|
1276
|
-
dict
|
|
2245
|
+
dict | None:
|
|
2246
|
+
Request response or None if the REST call fails.
|
|
2247
|
+
|
|
1277
2248
|
"""
|
|
1278
2249
|
|
|
1279
2250
|
resource_post_body_json = {}
|
|
1280
2251
|
|
|
1281
2252
|
request_url = "{}/{}/activate".format(self.config()["resourceUrl"], resource_id)
|
|
1282
2253
|
|
|
1283
|
-
logger.debug(
|
|
1284
|
-
"Activating resource -> '%s'; calling -> %s",
|
|
2254
|
+
self.logger.debug(
|
|
2255
|
+
"Activating resource -> '%s'; calling -> %s",
|
|
2256
|
+
resource_id,
|
|
2257
|
+
request_url,
|
|
1285
2258
|
)
|
|
1286
2259
|
|
|
1287
2260
|
return self.do_request(
|
|
@@ -1295,17 +2268,20 @@ class OTDS:
|
|
|
1295
2268
|
# end method definition
|
|
1296
2269
|
|
|
1297
2270
|
def get_access_roles(self) -> dict | None:
|
|
1298
|
-
"""Get a list of all OTDS access roles
|
|
2271
|
+
"""Get a list of all OTDS access roles.
|
|
1299
2272
|
|
|
1300
2273
|
Args:
|
|
1301
2274
|
None
|
|
2275
|
+
|
|
1302
2276
|
Returns:
|
|
1303
|
-
dict
|
|
2277
|
+
dict | None:
|
|
2278
|
+
Request response or None if the REST call fails.
|
|
2279
|
+
|
|
1304
2280
|
"""
|
|
1305
2281
|
|
|
1306
2282
|
request_url = self.config()["accessRoleUrl"]
|
|
1307
2283
|
|
|
1308
|
-
logger.debug("Retrieving access roles; calling -> %s", request_url)
|
|
2284
|
+
self.logger.debug("Retrieving access roles; calling -> %s", request_url)
|
|
1309
2285
|
|
|
1310
2286
|
return self.do_request(
|
|
1311
2287
|
url=request_url,
|
|
@@ -1317,17 +2293,25 @@ class OTDS:
|
|
|
1317
2293
|
# end method definition
|
|
1318
2294
|
|
|
1319
2295
|
def get_access_role(self, access_role: str) -> dict | None:
|
|
1320
|
-
"""Get an OTDS access role
|
|
2296
|
+
"""Get an OTDS access role.
|
|
1321
2297
|
|
|
1322
2298
|
Args:
|
|
1323
|
-
|
|
2299
|
+
access_role (str):
|
|
2300
|
+
The name of the access role.
|
|
2301
|
+
|
|
1324
2302
|
Returns:
|
|
1325
|
-
dict
|
|
2303
|
+
dict | None:
|
|
2304
|
+
Request response or None if the REST call fails.
|
|
2305
|
+
|
|
1326
2306
|
"""
|
|
1327
2307
|
|
|
1328
2308
|
request_url = self.config()["accessRoleUrl"] + "/" + access_role
|
|
1329
2309
|
|
|
1330
|
-
logger.debug(
|
|
2310
|
+
self.logger.debug(
|
|
2311
|
+
"Get access role -> '%s'; calling -> %s",
|
|
2312
|
+
access_role,
|
|
2313
|
+
request_url,
|
|
2314
|
+
)
|
|
1331
2315
|
|
|
1332
2316
|
return self.do_request(
|
|
1333
2317
|
url=request_url,
|
|
@@ -1339,29 +2323,39 @@ class OTDS:
|
|
|
1339
2323
|
# end method definition
|
|
1340
2324
|
|
|
1341
2325
|
def add_partition_to_access_role(
|
|
1342
|
-
self,
|
|
2326
|
+
self,
|
|
2327
|
+
access_role: str,
|
|
2328
|
+
partition: str,
|
|
2329
|
+
location: str = "",
|
|
1343
2330
|
) -> bool:
|
|
1344
|
-
"""Add an OTDS partition to an OTDS access role
|
|
2331
|
+
"""Add an OTDS partition to an OTDS access role.
|
|
1345
2332
|
|
|
1346
2333
|
Args:
|
|
1347
|
-
access_role (str):
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
2334
|
+
access_role (str):
|
|
2335
|
+
The name of the OTDS access role.
|
|
2336
|
+
partition (str):
|
|
2337
|
+
The name of the partition.
|
|
2338
|
+
location (str, optional):
|
|
2339
|
+
This is kind of a unique identifier DN (Distinguished Name)
|
|
2340
|
+
most of the times you will want to keep it to empty string ("")
|
|
2341
|
+
|
|
1351
2342
|
Returns:
|
|
1352
|
-
bool:
|
|
1353
|
-
|
|
2343
|
+
bool:
|
|
2344
|
+
True if partition is in access role or has been successfully added.
|
|
2345
|
+
False if partition has been not been added (error)
|
|
2346
|
+
|
|
1354
2347
|
"""
|
|
1355
2348
|
|
|
1356
2349
|
access_role_post_body_json = {
|
|
1357
|
-
"userPartitions": [{"name": partition, "location": location}]
|
|
2350
|
+
"userPartitions": [{"name": partition, "location": location}],
|
|
1358
2351
|
}
|
|
1359
2352
|
|
|
1360
2353
|
request_url = "{}/{}/members".format(
|
|
1361
|
-
self.config()["accessRoleUrl"],
|
|
2354
|
+
self.config()["accessRoleUrl"],
|
|
2355
|
+
access_role,
|
|
1362
2356
|
)
|
|
1363
2357
|
|
|
1364
|
-
logger.debug(
|
|
2358
|
+
self.logger.debug(
|
|
1365
2359
|
"Add user partition -> '%s' to access role -> '%s'; calling -> %s",
|
|
1366
2360
|
partition,
|
|
1367
2361
|
access_role,
|
|
@@ -1374,31 +2368,38 @@ class OTDS:
|
|
|
1374
2368
|
json_data=access_role_post_body_json,
|
|
1375
2369
|
timeout=None,
|
|
1376
2370
|
failure_message="Failed to add partition -> '{}' to access role -> '{}'".format(
|
|
1377
|
-
partition,
|
|
2371
|
+
partition,
|
|
2372
|
+
access_role,
|
|
1378
2373
|
),
|
|
1379
2374
|
parse_request_response=False,
|
|
1380
2375
|
)
|
|
1381
2376
|
|
|
1382
|
-
|
|
1383
|
-
return True
|
|
1384
|
-
|
|
1385
|
-
return False
|
|
2377
|
+
return bool(response and response.ok)
|
|
1386
2378
|
|
|
1387
2379
|
# end method definition
|
|
1388
2380
|
|
|
1389
2381
|
def add_user_to_access_role(
|
|
1390
|
-
self,
|
|
2382
|
+
self,
|
|
2383
|
+
access_role: str,
|
|
2384
|
+
user_id: str,
|
|
2385
|
+
location: str = "",
|
|
1391
2386
|
) -> bool:
|
|
1392
|
-
"""Add an OTDS user to an OTDS access role
|
|
2387
|
+
"""Add an OTDS user to an OTDS access role.
|
|
1393
2388
|
|
|
1394
2389
|
Args:
|
|
1395
|
-
access_role (str):
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
2390
|
+
access_role (str):
|
|
2391
|
+
The name of the OTDS access role.
|
|
2392
|
+
user_id (str):
|
|
2393
|
+
The ID of the user (= login name) to add to the access role.
|
|
2394
|
+
location (str, optional):
|
|
2395
|
+
This is kind of a unique identifier DN (Distinguished Name)
|
|
2396
|
+
most of the times you will want to keep it to empty string ("").
|
|
2397
|
+
|
|
1399
2398
|
Returns:
|
|
1400
|
-
bool:
|
|
1401
|
-
|
|
2399
|
+
bool:
|
|
2400
|
+
True if user is in access role or has been successfully added.
|
|
2401
|
+
False if user has not been added (error).
|
|
2402
|
+
|
|
1402
2403
|
"""
|
|
1403
2404
|
|
|
1404
2405
|
# get existing members to check if user is already a member:
|
|
@@ -1407,17 +2408,17 @@ class OTDS:
|
|
|
1407
2408
|
return False
|
|
1408
2409
|
|
|
1409
2410
|
# Checking if user already added to access role
|
|
1410
|
-
|
|
1411
|
-
for user in
|
|
2411
|
+
access_role_users = access_roles_get_response["accessRoleMembers"]["users"]
|
|
2412
|
+
for user in access_role_users:
|
|
1412
2413
|
if user["displayName"] == user_id:
|
|
1413
|
-
logger.debug(
|
|
2414
|
+
self.logger.debug(
|
|
1414
2415
|
"User -> '%s' already added to access role -> '%s'",
|
|
1415
2416
|
user_id,
|
|
1416
2417
|
access_role,
|
|
1417
2418
|
)
|
|
1418
2419
|
return True
|
|
1419
2420
|
|
|
1420
|
-
logger.debug(
|
|
2421
|
+
self.logger.debug(
|
|
1421
2422
|
"User -> '%s' is not yet in access role -> '%s' - adding...",
|
|
1422
2423
|
user_id,
|
|
1423
2424
|
access_role,
|
|
@@ -1425,14 +2426,15 @@ class OTDS:
|
|
|
1425
2426
|
|
|
1426
2427
|
# create payload for REST call:
|
|
1427
2428
|
access_role_post_body_json = {
|
|
1428
|
-
"users": [{"name": user_id, "location": location}]
|
|
2429
|
+
"users": [{"name": user_id, "location": location}],
|
|
1429
2430
|
}
|
|
1430
2431
|
|
|
1431
2432
|
request_url = "{}/{}/members".format(
|
|
1432
|
-
self.config()["accessRoleUrl"],
|
|
2433
|
+
self.config()["accessRoleUrl"],
|
|
2434
|
+
access_role,
|
|
1433
2435
|
)
|
|
1434
2436
|
|
|
1435
|
-
logger.debug(
|
|
2437
|
+
self.logger.debug(
|
|
1436
2438
|
"Add user -> %s to access role -> %s; calling -> %s",
|
|
1437
2439
|
user_id,
|
|
1438
2440
|
access_role,
|
|
@@ -1445,31 +2447,38 @@ class OTDS:
|
|
|
1445
2447
|
json_data=access_role_post_body_json,
|
|
1446
2448
|
timeout=None,
|
|
1447
2449
|
failure_message="Failed to add user -> '{}' to access role -> '{}'".format(
|
|
1448
|
-
user_id,
|
|
2450
|
+
user_id,
|
|
2451
|
+
access_role,
|
|
1449
2452
|
),
|
|
1450
2453
|
parse_request_response=False,
|
|
1451
2454
|
)
|
|
1452
2455
|
|
|
1453
|
-
|
|
1454
|
-
return True
|
|
1455
|
-
|
|
1456
|
-
return False
|
|
2456
|
+
return bool(response and response.ok)
|
|
1457
2457
|
|
|
1458
2458
|
# end method definition
|
|
1459
2459
|
|
|
1460
2460
|
def add_group_to_access_role(
|
|
1461
|
-
self,
|
|
2461
|
+
self,
|
|
2462
|
+
access_role: str,
|
|
2463
|
+
group: str,
|
|
2464
|
+
location: str = "",
|
|
1462
2465
|
) -> bool:
|
|
1463
|
-
"""Add an OTDS group to an OTDS access role
|
|
2466
|
+
"""Add an OTDS group to an OTDS access role.
|
|
1464
2467
|
|
|
1465
2468
|
Args:
|
|
1466
|
-
access_role (str):
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
2469
|
+
access_role (str):
|
|
2470
|
+
The name of the OTDS access role.
|
|
2471
|
+
group (str):
|
|
2472
|
+
The name of the group to add to the access role.
|
|
2473
|
+
location (str, optional):
|
|
2474
|
+
This is kind of a unique identifier DN (Distinguished Name)
|
|
2475
|
+
most of the times you will want to keep it to empty string ("").
|
|
2476
|
+
|
|
1470
2477
|
Returns:
|
|
1471
|
-
bool:
|
|
1472
|
-
|
|
2478
|
+
bool:
|
|
2479
|
+
True if group is in access role or has been successfully added.
|
|
2480
|
+
False if group has been not been added (error)
|
|
2481
|
+
|
|
1473
2482
|
"""
|
|
1474
2483
|
|
|
1475
2484
|
# get existing members to check if user is already a member:
|
|
@@ -1481,14 +2490,14 @@ class OTDS:
|
|
|
1481
2490
|
access_role_groups = access_roles_get_response["accessRoleMembers"]["groups"]
|
|
1482
2491
|
for access_role_group in access_role_groups:
|
|
1483
2492
|
if access_role_group["name"] == group:
|
|
1484
|
-
logger.debug(
|
|
2493
|
+
self.logger.debug(
|
|
1485
2494
|
"Group -> '%s' already added to access role -> '%s'",
|
|
1486
2495
|
group,
|
|
1487
2496
|
access_role,
|
|
1488
2497
|
)
|
|
1489
2498
|
return True
|
|
1490
2499
|
|
|
1491
|
-
logger.debug(
|
|
2500
|
+
self.logger.debug(
|
|
1492
2501
|
"Group -> '%s' is not yet in access role -> '%s' - adding...",
|
|
1493
2502
|
group,
|
|
1494
2503
|
access_role,
|
|
@@ -1498,10 +2507,11 @@ class OTDS:
|
|
|
1498
2507
|
access_role_post_body_json = {"groups": [{"name": group, "location": location}]}
|
|
1499
2508
|
|
|
1500
2509
|
request_url = "{}/{}/members".format(
|
|
1501
|
-
self.config()["accessRoleUrl"],
|
|
2510
|
+
self.config()["accessRoleUrl"],
|
|
2511
|
+
access_role,
|
|
1502
2512
|
)
|
|
1503
2513
|
|
|
1504
|
-
logger.debug(
|
|
2514
|
+
self.logger.debug(
|
|
1505
2515
|
"Add group -> '%s' to access role -> '%s'; calling -> %s",
|
|
1506
2516
|
group,
|
|
1507
2517
|
access_role,
|
|
@@ -1514,30 +2524,41 @@ class OTDS:
|
|
|
1514
2524
|
json_data=access_role_post_body_json,
|
|
1515
2525
|
timeout=None,
|
|
1516
2526
|
failure_message="Failed to add group -> '{}' to access role -> '{}'".format(
|
|
1517
|
-
group,
|
|
2527
|
+
group,
|
|
2528
|
+
access_role,
|
|
1518
2529
|
),
|
|
1519
2530
|
parse_request_response=False,
|
|
1520
2531
|
)
|
|
1521
2532
|
|
|
1522
|
-
|
|
1523
|
-
return True
|
|
1524
|
-
|
|
1525
|
-
return False
|
|
2533
|
+
return bool(response and response.ok)
|
|
1526
2534
|
|
|
1527
2535
|
# end method definition
|
|
1528
2536
|
|
|
1529
2537
|
def update_access_role_attributes(
|
|
1530
|
-
self,
|
|
2538
|
+
self,
|
|
2539
|
+
name: str,
|
|
2540
|
+
attribute_list: list,
|
|
1531
2541
|
) -> dict | None:
|
|
1532
|
-
"""Update some attributes of an existing OTDS
|
|
2542
|
+
"""Update some attributes of an existing OTDS access role.
|
|
1533
2543
|
|
|
1534
2544
|
Args:
|
|
1535
|
-
name (str):
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
2545
|
+
name (str):
|
|
2546
|
+
The name of the existing access role.
|
|
2547
|
+
attribute_list (list):
|
|
2548
|
+
A list of attribute name and attribute value pairs.
|
|
2549
|
+
The values need to be a list as well.
|
|
2550
|
+
Example values:
|
|
2551
|
+
[
|
|
2552
|
+
{
|
|
2553
|
+
name: "pushAllGroups",
|
|
2554
|
+
values: ["True"]
|
|
2555
|
+
}
|
|
2556
|
+
]
|
|
2557
|
+
|
|
1539
2558
|
Returns:
|
|
1540
|
-
dict
|
|
2559
|
+
dict | None:
|
|
2560
|
+
Request response or None if the REST call fails.
|
|
2561
|
+
|
|
1541
2562
|
"""
|
|
1542
2563
|
|
|
1543
2564
|
# Return if list is empty:
|
|
@@ -1547,14 +2568,14 @@ class OTDS:
|
|
|
1547
2568
|
# create payload for REST call:
|
|
1548
2569
|
access_role = self.get_access_role(name)
|
|
1549
2570
|
if not access_role:
|
|
1550
|
-
logger.error("Failed to get access role -> '%s'", name)
|
|
2571
|
+
self.logger.error("Failed to get access role -> '%s'", name)
|
|
1551
2572
|
return None
|
|
1552
2573
|
|
|
1553
2574
|
access_role_put_body_json = {"attributes": attribute_list}
|
|
1554
2575
|
|
|
1555
2576
|
request_url = "{}/{}/attributes".format(self.config()["accessRoleUrl"], name)
|
|
1556
2577
|
|
|
1557
|
-
logger.debug(
|
|
2578
|
+
self.logger.debug(
|
|
1558
2579
|
"Update access role -> '%s' with attributes -> %s; calling -> %s",
|
|
1559
2580
|
name,
|
|
1560
2581
|
str(access_role_put_body_json),
|
|
@@ -1582,24 +2603,31 @@ class OTDS:
|
|
|
1582
2603
|
"""Add a product license to an OTDS resource.
|
|
1583
2604
|
|
|
1584
2605
|
Args:
|
|
1585
|
-
path_to_license_file (str):
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
2606
|
+
path_to_license_file (str):
|
|
2607
|
+
A fully qualified filename of the license file.
|
|
2608
|
+
product_name (str):
|
|
2609
|
+
The product name.
|
|
2610
|
+
product_description (str):
|
|
2611
|
+
The product description.
|
|
2612
|
+
resource_id (str):
|
|
2613
|
+
OTDS resource ID (this is ID not the resource name!).
|
|
2614
|
+
update (bool, optional):
|
|
2615
|
+
Whether or not an existing license should be updated (default = True).
|
|
2616
|
+
|
|
1590
2617
|
Returns:
|
|
1591
|
-
dict
|
|
2618
|
+
dict | None:
|
|
2619
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2620
|
+
|
|
1592
2621
|
"""
|
|
1593
2622
|
|
|
1594
|
-
logger.debug("Reading license file -> '%s'...", path_to_license_file)
|
|
2623
|
+
self.logger.debug("Reading license file -> '%s'...", path_to_license_file)
|
|
1595
2624
|
try:
|
|
1596
|
-
with open(path_to_license_file,
|
|
2625
|
+
with open(path_to_license_file, encoding="UTF-8") as license_file:
|
|
1597
2626
|
license_content = license_file.read()
|
|
1598
|
-
except
|
|
1599
|
-
logger.error(
|
|
1600
|
-
"Error opening license file -> '%s'
|
|
2627
|
+
except OSError:
|
|
2628
|
+
self.logger.error(
|
|
2629
|
+
"Error opening license file -> '%s'!",
|
|
1601
2630
|
path_to_license_file,
|
|
1602
|
-
exception.strerror,
|
|
1603
2631
|
)
|
|
1604
2632
|
return None
|
|
1605
2633
|
|
|
@@ -1620,14 +2648,14 @@ class OTDS:
|
|
|
1620
2648
|
if existing_license:
|
|
1621
2649
|
request_url += "/" + existing_license[0]["id"]
|
|
1622
2650
|
else:
|
|
1623
|
-
logger.debug(
|
|
2651
|
+
self.logger.debug(
|
|
1624
2652
|
"No existing license found for resource -> '%s' - adding a new license...",
|
|
1625
2653
|
resource_id,
|
|
1626
2654
|
)
|
|
1627
2655
|
# change strategy to create a new license:
|
|
1628
2656
|
update = False
|
|
1629
2657
|
|
|
1630
|
-
logger.debug(
|
|
2658
|
+
self.logger.debug(
|
|
1631
2659
|
"Adding product license -> '%s' for product -> '%s' to resource ->'%s'; calling -> %s",
|
|
1632
2660
|
path_to_license_file,
|
|
1633
2661
|
product_description,
|
|
@@ -1643,7 +2671,8 @@ class OTDS:
|
|
|
1643
2671
|
json_data=license_post_body_json,
|
|
1644
2672
|
timeout=None,
|
|
1645
2673
|
failure_message="Failed to update product license -> '{}' for product -> '{}'".format(
|
|
1646
|
-
path_to_license_file,
|
|
2674
|
+
path_to_license_file,
|
|
2675
|
+
product_description,
|
|
1647
2676
|
),
|
|
1648
2677
|
)
|
|
1649
2678
|
else:
|
|
@@ -1654,43 +2683,45 @@ class OTDS:
|
|
|
1654
2683
|
json_data=license_post_body_json,
|
|
1655
2684
|
timeout=None,
|
|
1656
2685
|
failure_message="Failed to add product license -> '{}' for product -> '{}'".format(
|
|
1657
|
-
path_to_license_file,
|
|
2686
|
+
path_to_license_file,
|
|
2687
|
+
product_description,
|
|
1658
2688
|
),
|
|
1659
2689
|
)
|
|
1660
2690
|
|
|
1661
2691
|
# end method definition
|
|
1662
2692
|
|
|
1663
|
-
def get_license_for_resource(self, resource_id: str):
|
|
2693
|
+
def get_license_for_resource(self, resource_id: str) -> dict | None:
|
|
1664
2694
|
"""Get a product license for a resource in OTDS.
|
|
1665
2695
|
|
|
1666
2696
|
Args:
|
|
1667
|
-
resource_id (str):
|
|
2697
|
+
resource_id (str):
|
|
2698
|
+
The OTDS resource ID (this is ID not the resource name!).
|
|
2699
|
+
|
|
1668
2700
|
Returns:
|
|
1669
|
-
|
|
2701
|
+
dict | None:
|
|
2702
|
+
Licenses for a resource or None if the REST call fails.
|
|
2703
|
+
|
|
2704
|
+
Example:
|
|
2705
|
+
{
|
|
2706
|
+
'_oTLicenseType': 'NON-PRODUCTION',
|
|
2707
|
+
'_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
2708
|
+
'_oTLicenseResourceName': 'cs',
|
|
2709
|
+
'_oTLicenseProduct': 'EXTENDED_ECM',
|
|
2710
|
+
'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
2711
|
+
'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
2712
|
+
'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
2713
|
+
'description': 'CS license',
|
|
2714
|
+
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
2715
|
+
}
|
|
1670
2716
|
|
|
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
|
-
}
|
|
1683
2717
|
"""
|
|
1684
2718
|
|
|
1685
|
-
request_url = (
|
|
1686
|
-
self.license_url()
|
|
1687
|
-
+ "/assignedlicenses?resourceID="
|
|
1688
|
-
+ resource_id
|
|
1689
|
-
+ "&validOnly=false"
|
|
1690
|
-
)
|
|
2719
|
+
request_url = self.license_url() + "/assignedlicenses?resourceID=" + resource_id + "&validOnly=false"
|
|
1691
2720
|
|
|
1692
|
-
logger.debug(
|
|
1693
|
-
"Get license for resource -> %s; calling -> %s",
|
|
2721
|
+
self.logger.debug(
|
|
2722
|
+
"Get license for resource -> %s; calling -> %s",
|
|
2723
|
+
resource_id,
|
|
2724
|
+
request_url,
|
|
1694
2725
|
)
|
|
1695
2726
|
|
|
1696
2727
|
response = self.do_request(
|
|
@@ -1698,7 +2729,7 @@ class OTDS:
|
|
|
1698
2729
|
method="GET",
|
|
1699
2730
|
timeout=None,
|
|
1700
2731
|
failure_message="Failed to get license for resource -> '{}'".format(
|
|
1701
|
-
resource_id
|
|
2732
|
+
resource_id,
|
|
1702
2733
|
),
|
|
1703
2734
|
)
|
|
1704
2735
|
|
|
@@ -1713,15 +2744,20 @@ class OTDS:
|
|
|
1713
2744
|
"""Delete a product license for a resource in OTDS.
|
|
1714
2745
|
|
|
1715
2746
|
Args:
|
|
1716
|
-
resource_id (str):
|
|
1717
|
-
|
|
2747
|
+
resource_id (str):
|
|
2748
|
+
The OTDS resource ID (this is ID not the resource name!).
|
|
2749
|
+
license_id (str):
|
|
2750
|
+
The OTDS license ID (this is the ID not the license name!).
|
|
2751
|
+
|
|
1718
2752
|
Returns:
|
|
1719
|
-
bool:
|
|
2753
|
+
bool:
|
|
2754
|
+
True if successful or False if the REST call fails
|
|
2755
|
+
|
|
1720
2756
|
"""
|
|
1721
2757
|
|
|
1722
2758
|
request_url = "{}/{}".format(self.license_url(), license_id)
|
|
1723
2759
|
|
|
1724
|
-
logger.debug(
|
|
2760
|
+
self.logger.debug(
|
|
1725
2761
|
"Deleting product license -> '%s' from resource -> '%s'; calling -> %s",
|
|
1726
2762
|
license_id,
|
|
1727
2763
|
resource_id,
|
|
@@ -1733,15 +2769,13 @@ class OTDS:
|
|
|
1733
2769
|
method="DELETE",
|
|
1734
2770
|
timeout=None,
|
|
1735
2771
|
failure_message="Failed to delete license -> '{}' for resource -> '{}'".format(
|
|
1736
|
-
license_id,
|
|
2772
|
+
license_id,
|
|
2773
|
+
resource_id,
|
|
1737
2774
|
),
|
|
1738
2775
|
parse_request_response=False,
|
|
1739
2776
|
)
|
|
1740
2777
|
|
|
1741
|
-
|
|
1742
|
-
return True
|
|
1743
|
-
|
|
1744
|
-
return False
|
|
2778
|
+
return bool(response and response.ok)
|
|
1745
2779
|
|
|
1746
2780
|
# end method definition
|
|
1747
2781
|
|
|
@@ -1756,27 +2790,53 @@ class OTDS:
|
|
|
1756
2790
|
) -> bool:
|
|
1757
2791
|
"""Assign an OTDS user to a product license (feature) in OTDS.
|
|
1758
2792
|
|
|
2793
|
+
licenses have this format:
|
|
2794
|
+
{
|
|
2795
|
+
'_oTLicenseType': 'NON-PRODUCTION',
|
|
2796
|
+
'_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
2797
|
+
'_oTLicenseResourceName': 'cs',
|
|
2798
|
+
'_oTLicenseProduct': 'EXTENDED_ECM',
|
|
2799
|
+
'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
2800
|
+
'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
2801
|
+
'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
2802
|
+
'description': 'CS license',
|
|
2803
|
+
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
2804
|
+
}
|
|
2805
|
+
|
|
1759
2806
|
Args:
|
|
1760
|
-
partition (str):
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2807
|
+
partition (str):
|
|
2808
|
+
The user partition in OTDS, e.g. "Content Server Members".
|
|
2809
|
+
user_id (str):
|
|
2810
|
+
The ID of the user (= login name) to assign to the license.
|
|
2811
|
+
resource_id (str):
|
|
2812
|
+
The OTDS resource ID (this is ID not the resource name!).
|
|
2813
|
+
license_feature (str):
|
|
2814
|
+
The name of the license feature.
|
|
2815
|
+
license_name (str):
|
|
2816
|
+
The name of the license to assign.
|
|
2817
|
+
license_type (str, optional):
|
|
2818
|
+
The type of the license. Default is "Full", Extended ECM also has "Occasional".
|
|
2819
|
+
|
|
1766
2820
|
Returns:
|
|
1767
|
-
bool:
|
|
2821
|
+
bool:
|
|
2822
|
+
True if successful or False if the REST call fails or the license is not found.
|
|
2823
|
+
|
|
1768
2824
|
"""
|
|
1769
2825
|
|
|
1770
2826
|
licenses = self.get_license_for_resource(resource_id)
|
|
2827
|
+
if not licenses:
|
|
2828
|
+
self.logger.error(
|
|
2829
|
+
"Resource with ID -> '%s' does not exist or has no licenses",
|
|
2830
|
+
resource_id,
|
|
2831
|
+
)
|
|
2832
|
+
return False
|
|
1771
2833
|
|
|
1772
2834
|
for lic in licenses:
|
|
1773
2835
|
if lic["_oTLicenseProduct"] == license_name:
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
except UnboundLocalError:
|
|
1779
|
-
logger.error(
|
|
2836
|
+
license_id = lic["id"]
|
|
2837
|
+
break
|
|
2838
|
+
else:
|
|
2839
|
+
self.logger.error(
|
|
1780
2840
|
"Cannot find license -> '%s' for resource -> %s",
|
|
1781
2841
|
license_name,
|
|
1782
2842
|
resource_id,
|
|
@@ -1787,7 +2847,7 @@ class OTDS:
|
|
|
1787
2847
|
if user:
|
|
1788
2848
|
user_location = user["location"]
|
|
1789
2849
|
else:
|
|
1790
|
-
logger.error("Cannot find location for user -> '%s'", user_id)
|
|
2850
|
+
self.logger.error("Cannot find location for user -> '%s'", user_id)
|
|
1791
2851
|
return False
|
|
1792
2852
|
|
|
1793
2853
|
license_post_body_json = {
|
|
@@ -1797,12 +2857,12 @@ class OTDS:
|
|
|
1797
2857
|
"values": [{"name": "counter", "values": [license_feature]}],
|
|
1798
2858
|
}
|
|
1799
2859
|
|
|
1800
|
-
request_url = self.license_url() + "/object/" +
|
|
2860
|
+
request_url = self.license_url() + "/object/" + license_id
|
|
1801
2861
|
|
|
1802
|
-
logger.debug(
|
|
2862
|
+
self.logger.debug(
|
|
1803
2863
|
"Assign license feature -> '%s' of license -> '%s' associated with resource -> '%s' to user -> '%s'; calling -> %s",
|
|
1804
2864
|
license_feature,
|
|
1805
|
-
|
|
2865
|
+
license_id,
|
|
1806
2866
|
resource_id,
|
|
1807
2867
|
user_id,
|
|
1808
2868
|
request_url,
|
|
@@ -1814,13 +2874,15 @@ class OTDS:
|
|
|
1814
2874
|
json_data=license_post_body_json,
|
|
1815
2875
|
timeout=None,
|
|
1816
2876
|
failure_message="Failed to add license feature -> '{}' associated with resource -> '{}' to user -> '{}'".format(
|
|
1817
|
-
license_feature,
|
|
2877
|
+
license_feature,
|
|
2878
|
+
resource_id,
|
|
2879
|
+
user_id,
|
|
1818
2880
|
),
|
|
1819
2881
|
parse_request_response=False,
|
|
1820
2882
|
)
|
|
1821
2883
|
|
|
1822
2884
|
if response and response.ok:
|
|
1823
|
-
logger.debug(
|
|
2885
|
+
self.logger.debug(
|
|
1824
2886
|
"Added license feature -> '%s' to user -> '%s'",
|
|
1825
2887
|
license_feature,
|
|
1826
2888
|
user_id,
|
|
@@ -1841,44 +2903,51 @@ class OTDS:
|
|
|
1841
2903
|
) -> bool:
|
|
1842
2904
|
"""Assign an OTDS partition to a product license (feature).
|
|
1843
2905
|
|
|
2906
|
+
licenses have this format:
|
|
2907
|
+
{
|
|
2908
|
+
'_oTLicenseType': 'NON-PRODUCTION',
|
|
2909
|
+
'_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
2910
|
+
'_oTLicenseResourceName': 'cs',
|
|
2911
|
+
'_oTLicenseProduct': 'EXTENDED_ECM',
|
|
2912
|
+
'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
2913
|
+
'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
2914
|
+
'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
2915
|
+
'description': 'CS license',
|
|
2916
|
+
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
2917
|
+
}
|
|
2918
|
+
|
|
1844
2919
|
Args:
|
|
1845
|
-
partition_name (str):
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
2920
|
+
partition_name (str):
|
|
2921
|
+
The user partition in OTDS, e.g. "Content Server Members".
|
|
2922
|
+
resource_id (str):
|
|
2923
|
+
The OTDS resource ID (this is ID not the resource name!).
|
|
2924
|
+
license_feature (str):
|
|
2925
|
+
The name of the license feature, e.g. "X2" or "ADDON_ENGINEERING".
|
|
2926
|
+
license_name (str):
|
|
2927
|
+
The name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG".
|
|
2928
|
+
license_type (str, optional):
|
|
2929
|
+
The license type. Default is "Full", Extended ECM also has "Occasional"
|
|
2930
|
+
|
|
1850
2931
|
Returns:
|
|
1851
|
-
bool:
|
|
2932
|
+
bool:
|
|
2933
|
+
True if successful or False if the REST call fails or the license is not found.
|
|
2934
|
+
|
|
1852
2935
|
"""
|
|
1853
2936
|
|
|
1854
2937
|
licenses = self.get_license_for_resource(resource_id)
|
|
1855
2938
|
if not licenses:
|
|
1856
|
-
logger.error(
|
|
2939
|
+
self.logger.error(
|
|
1857
2940
|
"Resource with ID -> '%s' does not exist or has no licenses",
|
|
1858
2941
|
resource_id,
|
|
1859
2942
|
)
|
|
1860
2943
|
return False
|
|
1861
2944
|
|
|
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
2945
|
for lic in licenses:
|
|
1875
2946
|
if lic["_oTLicenseProduct"] == license_name:
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
except UnboundLocalError:
|
|
1881
|
-
logger.error(
|
|
2947
|
+
license_id = lic["id"]
|
|
2948
|
+
break
|
|
2949
|
+
else:
|
|
2950
|
+
self.logger.error(
|
|
1882
2951
|
"Cannot find license -> %s for resource -> %s",
|
|
1883
2952
|
license_name,
|
|
1884
2953
|
resource_id,
|
|
@@ -1892,12 +2961,12 @@ class OTDS:
|
|
|
1892
2961
|
"values": [{"name": "counter", "values": [license_feature]}],
|
|
1893
2962
|
}
|
|
1894
2963
|
|
|
1895
|
-
request_url = self.license_url() + "/object/" +
|
|
2964
|
+
request_url = self.license_url() + "/object/" + license_id
|
|
1896
2965
|
|
|
1897
|
-
logger.debug(
|
|
2966
|
+
self.logger.debug(
|
|
1898
2967
|
"Assign license feature -> '%s' of license -> '%s' associated with resource -> '%s' to partition -> '%s'; calling -> %s",
|
|
1899
2968
|
license_feature,
|
|
1900
|
-
|
|
2969
|
+
license_id,
|
|
1901
2970
|
resource_id,
|
|
1902
2971
|
partition_name,
|
|
1903
2972
|
request_url,
|
|
@@ -1909,13 +2978,15 @@ class OTDS:
|
|
|
1909
2978
|
json_data=license_post_body_json,
|
|
1910
2979
|
timeout=None,
|
|
1911
2980
|
failure_message="Failed to add license feature -> '{}' associated with resource -> '{}' to partition -> '{}'".format(
|
|
1912
|
-
license_feature,
|
|
2981
|
+
license_feature,
|
|
2982
|
+
resource_id,
|
|
2983
|
+
partition_name,
|
|
1913
2984
|
),
|
|
1914
2985
|
parse_request_response=False,
|
|
1915
2986
|
)
|
|
1916
2987
|
|
|
1917
2988
|
if response and response.ok:
|
|
1918
|
-
logger.debug(
|
|
2989
|
+
self.logger.debug(
|
|
1919
2990
|
"Added license feature -> '%s' to partition -> '%s'",
|
|
1920
2991
|
license_feature,
|
|
1921
2992
|
partition_name,
|
|
@@ -1932,73 +3003,89 @@ class OTDS:
|
|
|
1932
3003
|
license_feature: str,
|
|
1933
3004
|
license_name: str,
|
|
1934
3005
|
) -> dict | None:
|
|
1935
|
-
"""Return the licensed objects
|
|
1936
|
-
|
|
3006
|
+
"""Return the licensed objects for a license + license feature associated with an OTDS resource (like "cs").
|
|
3007
|
+
|
|
3008
|
+
Licensed objects can be users, groups, or partitions.
|
|
3009
|
+
|
|
3010
|
+
licenses have this format:
|
|
3011
|
+
{
|
|
3012
|
+
'_oTLicenseType': 'NON-PRODUCTION',
|
|
3013
|
+
'_oTLicenseResource': '7382094f-a434-4714-9696-82864b6803da',
|
|
3014
|
+
'_oTLicenseResourceName': 'cs',
|
|
3015
|
+
'_oTLicenseProduct': 'EXTENDED_ECM',
|
|
3016
|
+
'name': 'EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da',
|
|
3017
|
+
'location': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
3018
|
+
'id': 'cn=EXTENDED_ECM¹7382094f-a434-4714-9696-82864b6803da,ou=Licenses,dc=identity,dc=opentext,dc=net',
|
|
3019
|
+
'description': 'CS license',
|
|
3020
|
+
'values': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, ...]
|
|
3021
|
+
}
|
|
1937
3022
|
|
|
1938
3023
|
Args:
|
|
1939
|
-
resource_id (str):
|
|
1940
|
-
|
|
1941
|
-
|
|
3024
|
+
resource_id (str):
|
|
3025
|
+
The OTDS resource ID (this is ID not the resource name!).
|
|
3026
|
+
license_feature (str):
|
|
3027
|
+
The name of the license feature, e.g. "X2" or "ADDON_ENGINEERING".
|
|
3028
|
+
license_name (str):
|
|
3029
|
+
The name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG".
|
|
3030
|
+
|
|
1942
3031
|
Returns:
|
|
1943
|
-
dict
|
|
3032
|
+
dict | None:
|
|
3033
|
+
The data structure of licensed objects or None in case of an error.
|
|
1944
3034
|
|
|
1945
|
-
|
|
3035
|
+
Example:
|
|
1946
3036
|
{
|
|
1947
3037
|
'status': 0,
|
|
1948
3038
|
'displayString': 'Success',
|
|
1949
3039
|
'exceptions': None,
|
|
1950
3040
|
'retValue': 0,
|
|
1951
|
-
'listGroupsResults': {
|
|
1952
|
-
|
|
1953
|
-
|
|
3041
|
+
'listGroupsResults': {
|
|
3042
|
+
'groups': [...],
|
|
3043
|
+
'actualPageSize': 0,
|
|
3044
|
+
'nextPageCookie': None,
|
|
3045
|
+
'requestedPageSize': 250
|
|
3046
|
+
},
|
|
3047
|
+
'listUsersResults': {
|
|
3048
|
+
'users': [...],
|
|
3049
|
+
'actualPageSize': 53,
|
|
3050
|
+
'nextPageCookie': None,
|
|
3051
|
+
'requestedPageSize': 250
|
|
3052
|
+
},
|
|
3053
|
+
'listUserPartitionResult': {
|
|
3054
|
+
'_userPartitions': [...],
|
|
3055
|
+
'warningMessage': None,
|
|
3056
|
+
'actualPageSize': 0,
|
|
3057
|
+
'nextPageCookie': None,
|
|
3058
|
+
'requestedPageSize': 250
|
|
3059
|
+
},
|
|
1954
3060
|
'version': 1
|
|
1955
3061
|
}
|
|
3062
|
+
|
|
1956
3063
|
"""
|
|
1957
3064
|
|
|
1958
3065
|
licenses = self.get_license_for_resource(resource_id)
|
|
1959
3066
|
if not licenses:
|
|
1960
|
-
logger.error(
|
|
3067
|
+
self.logger.error(
|
|
1961
3068
|
"Resource with ID -> '%s' does not exist or has no licenses",
|
|
1962
3069
|
resource_id,
|
|
1963
3070
|
)
|
|
1964
3071
|
return False
|
|
1965
3072
|
|
|
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
3073
|
for lic in licenses:
|
|
1979
3074
|
if lic["_oTLicenseProduct"] == license_name:
|
|
1980
3075
|
license_location = lic["location"]
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
except UnboundLocalError:
|
|
1985
|
-
logger.error(
|
|
3076
|
+
break
|
|
3077
|
+
else:
|
|
3078
|
+
self.logger.error(
|
|
1986
3079
|
"Cannot find license -> %s for resource -> %s",
|
|
1987
3080
|
license_name,
|
|
1988
3081
|
resource_id,
|
|
1989
3082
|
)
|
|
1990
3083
|
return False
|
|
1991
3084
|
|
|
1992
|
-
request_url = (
|
|
1993
|
-
self.license_url()
|
|
1994
|
-
+ "/object/"
|
|
1995
|
-
+ license_location
|
|
1996
|
-
+ "?counter="
|
|
1997
|
-
+ license_feature
|
|
1998
|
-
)
|
|
3085
|
+
request_url = self.license_url() + "/object/" + license_location + "?counter=" + license_feature
|
|
1999
3086
|
|
|
2000
|
-
logger.debug(
|
|
2001
|
-
"Get licensed objects for license -> %s and license feature -> %s associated with resource -> %s; calling -> %s",
|
|
3087
|
+
self.logger.debug(
|
|
3088
|
+
"Get licensed objects for license -> '%s' and license feature -> '%s' associated with resource -> '%s'; calling -> %s",
|
|
2002
3089
|
license_name,
|
|
2003
3090
|
license_feature,
|
|
2004
3091
|
resource_id,
|
|
@@ -2010,25 +3097,37 @@ class OTDS:
|
|
|
2010
3097
|
method="GET",
|
|
2011
3098
|
timeout=None,
|
|
2012
3099
|
failure_message="Failed to get licensed objects for license -> '{}' and license feature -> '{}' associated with resource -> '{}'".format(
|
|
2013
|
-
license_name,
|
|
3100
|
+
license_name,
|
|
3101
|
+
license_feature,
|
|
3102
|
+
resource_id,
|
|
2014
3103
|
),
|
|
2015
3104
|
)
|
|
2016
3105
|
|
|
2017
3106
|
# end method definition
|
|
2018
3107
|
|
|
2019
3108
|
def is_user_licensed(
|
|
2020
|
-
self,
|
|
3109
|
+
self,
|
|
3110
|
+
user_name: str,
|
|
3111
|
+
resource_id: str,
|
|
3112
|
+
license_feature: str,
|
|
3113
|
+
license_name: str,
|
|
2021
3114
|
) -> bool:
|
|
2022
3115
|
"""Check if a user is licensed for a license and license feature associated with a particular OTDS resource.
|
|
2023
3116
|
|
|
2024
3117
|
Args:
|
|
2025
|
-
user_name (str):
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
3118
|
+
user_name (str):
|
|
3119
|
+
The login name of the OTDS user.
|
|
3120
|
+
resource_id (str):
|
|
3121
|
+
The OTDS resource ID (this is ID not the resource name!).
|
|
3122
|
+
license_feature (str):
|
|
3123
|
+
The name of the license feature, e.g. "X2" or "ADDON_ENGINEERING".
|
|
3124
|
+
license_name (str):
|
|
3125
|
+
The name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG".
|
|
2029
3126
|
|
|
2030
3127
|
Returns:
|
|
2031
|
-
bool:
|
|
3128
|
+
bool:
|
|
3129
|
+
True if the user is licensed and False otherwise.
|
|
3130
|
+
|
|
2032
3131
|
"""
|
|
2033
3132
|
|
|
2034
3133
|
response = self.get_licensed_objects(
|
|
@@ -2050,26 +3149,33 @@ class OTDS:
|
|
|
2050
3149
|
None,
|
|
2051
3150
|
)
|
|
2052
3151
|
|
|
2053
|
-
|
|
2054
|
-
return True
|
|
2055
|
-
|
|
2056
|
-
return False
|
|
3152
|
+
return bool(user)
|
|
2057
3153
|
|
|
2058
3154
|
# end method definition
|
|
2059
3155
|
|
|
2060
3156
|
def is_group_licensed(
|
|
2061
|
-
self,
|
|
3157
|
+
self,
|
|
3158
|
+
group_name: str,
|
|
3159
|
+
resource_id: str,
|
|
3160
|
+
license_feature: str,
|
|
3161
|
+
license_name: str,
|
|
2062
3162
|
) -> bool:
|
|
2063
3163
|
"""Check if a group is licensed for a license and license feature associated with a particular OTDS resource.
|
|
2064
3164
|
|
|
2065
3165
|
Args:
|
|
2066
|
-
group_name (str):
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
3166
|
+
group_name (str):
|
|
3167
|
+
The name of the OTDS user group.
|
|
3168
|
+
resource_id (str):
|
|
3169
|
+
The OTDS resource ID (this is ID not the resource name!).
|
|
3170
|
+
license_feature (str):
|
|
3171
|
+
The name of the license feature, e.g. "X2" or "ADDON_ENGINEERING".
|
|
3172
|
+
license_name (str):
|
|
3173
|
+
The name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG".
|
|
2070
3174
|
|
|
2071
3175
|
Returns:
|
|
2072
|
-
bool:
|
|
3176
|
+
bool:
|
|
3177
|
+
True if the group is licensed and False otherwise.
|
|
3178
|
+
|
|
2073
3179
|
"""
|
|
2074
3180
|
|
|
2075
3181
|
response = self.get_licensed_objects(
|
|
@@ -2091,10 +3197,7 @@ class OTDS:
|
|
|
2091
3197
|
None,
|
|
2092
3198
|
)
|
|
2093
3199
|
|
|
2094
|
-
|
|
2095
|
-
return True
|
|
2096
|
-
|
|
2097
|
-
return False
|
|
3200
|
+
return bool(group)
|
|
2098
3201
|
|
|
2099
3202
|
# end method definition
|
|
2100
3203
|
|
|
@@ -2105,16 +3208,22 @@ class OTDS:
|
|
|
2105
3208
|
license_feature: str,
|
|
2106
3209
|
license_name: str,
|
|
2107
3210
|
) -> bool:
|
|
2108
|
-
"""Check if a partition is licensed for a license
|
|
3211
|
+
"""Check if a partition is licensed for a license feature associated with a particular OTDS resource.
|
|
2109
3212
|
|
|
2110
3213
|
Args:
|
|
2111
|
-
partition_name (str):
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
3214
|
+
partition_name (str):
|
|
3215
|
+
The name of the OTDS user partition, e.g. "Content Server Members".
|
|
3216
|
+
resource_id (str):
|
|
3217
|
+
The OTDS resource ID (this is ID not the resource name!).
|
|
3218
|
+
license_feature (str):
|
|
3219
|
+
The name of the license feature, e.g. "X2" or "ADDON_ENGINEERING".
|
|
3220
|
+
license_name (str):
|
|
3221
|
+
The name of the license to assign, e.g. "EXTENDED_ECM" or "INTELLGENT_VIEWIMG".
|
|
2115
3222
|
|
|
2116
3223
|
Returns:
|
|
2117
|
-
bool:
|
|
3224
|
+
bool:
|
|
3225
|
+
True if the partition is licensed and False otherwise.
|
|
3226
|
+
|
|
2118
3227
|
"""
|
|
2119
3228
|
|
|
2120
3229
|
response = self.get_licensed_objects(
|
|
@@ -2136,124 +3245,117 @@ class OTDS:
|
|
|
2136
3245
|
None,
|
|
2137
3246
|
)
|
|
2138
3247
|
|
|
2139
|
-
|
|
2140
|
-
return True
|
|
2141
|
-
|
|
2142
|
-
return False
|
|
3248
|
+
return bool(partition)
|
|
2143
3249
|
|
|
2144
3250
|
# end method definition
|
|
2145
|
-
|
|
2146
|
-
def import_synchronized_partition_members(self, name: str) ->
|
|
2147
|
-
"""Import users and groups to partition
|
|
3251
|
+
|
|
3252
|
+
def import_synchronized_partition_members(self, name: str) -> bool:
|
|
3253
|
+
"""Import users and groups to partition.
|
|
2148
3254
|
|
|
2149
3255
|
Args:
|
|
2150
|
-
name (str):
|
|
3256
|
+
name (str):
|
|
3257
|
+
The name of the partition in which users need to be imported.
|
|
3258
|
+
|
|
2151
3259
|
Returns:
|
|
2152
|
-
|
|
3260
|
+
bool:
|
|
3261
|
+
True = Success, False = Error.
|
|
3262
|
+
|
|
2153
3263
|
"""
|
|
3264
|
+
|
|
2154
3265
|
command = {"command": "import"}
|
|
2155
|
-
request_url = self.synchronized_partition_url() + f
|
|
2156
|
-
|
|
2157
|
-
|
|
3266
|
+
request_url = self.synchronized_partition_url() + f"/{name}/command"
|
|
3267
|
+
|
|
3268
|
+
self.logger.debug(
|
|
3269
|
+
"Importing users and groups into partition -> '%s'; calling -> %s",
|
|
2158
3270
|
name,
|
|
2159
3271
|
request_url,
|
|
2160
3272
|
)
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
)
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
self.authenticate(revalidate=True)
|
|
2176
|
-
retries += 1
|
|
2177
|
-
else:
|
|
2178
|
-
logger.error(
|
|
2179
|
-
"Failed to Import users and groups to synchronized partition -> %s; error -> %s (%s)",
|
|
2180
|
-
name,
|
|
2181
|
-
response.text,
|
|
2182
|
-
response.status_code,
|
|
2183
|
-
)
|
|
2184
|
-
return None
|
|
2185
|
-
|
|
3273
|
+
|
|
3274
|
+
response = self.do_request(
|
|
3275
|
+
url=request_url,
|
|
3276
|
+
method="POST",
|
|
3277
|
+
json_data=command,
|
|
3278
|
+
timeout=None,
|
|
3279
|
+
failure_message="Failed to import users and groups to synchronized partition -> '{}'".format(
|
|
3280
|
+
name,
|
|
3281
|
+
),
|
|
3282
|
+
parse_request_response=False,
|
|
3283
|
+
)
|
|
3284
|
+
|
|
3285
|
+
return bool(response and response.ok and response.status_code == 204)
|
|
3286
|
+
|
|
2186
3287
|
# end of method definition
|
|
2187
|
-
|
|
2188
|
-
def add_synchronized_partition(
|
|
2189
|
-
|
|
3288
|
+
|
|
3289
|
+
def add_synchronized_partition(
|
|
3290
|
+
self,
|
|
3291
|
+
name: str,
|
|
3292
|
+
description: str,
|
|
3293
|
+
data: dict,
|
|
3294
|
+
) -> dict | None:
|
|
3295
|
+
"""Add a new synchronized partition to OTDS.
|
|
2190
3296
|
|
|
2191
3297
|
Args:
|
|
2192
|
-
name (str):
|
|
2193
|
-
|
|
2194
|
-
|
|
3298
|
+
name (str):
|
|
3299
|
+
The name of the new synchronized partition.
|
|
3300
|
+
description (str):
|
|
3301
|
+
The description of the new synchronized partition.
|
|
3302
|
+
data (dict):
|
|
3303
|
+
The data for creating synchronized partition
|
|
3304
|
+
|
|
2195
3305
|
Returns:
|
|
2196
|
-
dict
|
|
3306
|
+
dict | None:
|
|
3307
|
+
Request response or None if the creation fails.
|
|
3308
|
+
|
|
2197
3309
|
"""
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
],
|
|
2206
|
-
"basicInfo": {
|
|
2207
|
-
},
|
|
2208
|
-
"basicAttributes": []
|
|
3310
|
+
|
|
3311
|
+
synchronized_partition_post_body_json = {
|
|
3312
|
+
"ipConnectionParameter": [],
|
|
3313
|
+
"ipAuthentication": {},
|
|
3314
|
+
"objectClassNameMapping": [],
|
|
3315
|
+
"basicInfo": {},
|
|
3316
|
+
"basicAttributes": [],
|
|
2209
3317
|
}
|
|
2210
|
-
|
|
3318
|
+
synchronized_partition_post_body_json.update(data)
|
|
3319
|
+
|
|
2211
3320
|
request_url = self.synchronized_partition_url()
|
|
2212
|
-
logger.debug(
|
|
2213
|
-
"Adding synchronized partition -> %s (%s); calling -> %s",
|
|
3321
|
+
self.logger.debug(
|
|
3322
|
+
"Adding synchronized partition -> '%s' ('%s'); calling -> %s",
|
|
2214
3323
|
name,
|
|
2215
3324
|
description,
|
|
2216
3325
|
request_url,
|
|
2217
3326
|
)
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
if response.ok:
|
|
2229
|
-
return self.parse_request_response(response)
|
|
2230
|
-
# Check if Session has expired - then re-authenticate and try once more
|
|
2231
|
-
elif response.status_code == 401 and retries == 0:
|
|
2232
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
2233
|
-
self.authenticate(revalidate=True)
|
|
2234
|
-
retries += 1
|
|
2235
|
-
else:
|
|
2236
|
-
logger.error(
|
|
2237
|
-
"Failed to add synchronized partition -> %s; error -> %s (%s)",
|
|
2238
|
-
name,
|
|
2239
|
-
response.text,
|
|
2240
|
-
response.status_code,
|
|
2241
|
-
)
|
|
2242
|
-
return None
|
|
2243
|
-
|
|
3327
|
+
synchronized_partition_post_body_json["ipAuthentication"]["bindPassword"] = self.config()["bindPassword"]
|
|
3328
|
+
|
|
3329
|
+
return self.do_request(
|
|
3330
|
+
url=request_url,
|
|
3331
|
+
method="POST",
|
|
3332
|
+
json_data=synchronized_partition_post_body_json,
|
|
3333
|
+
timeout=None,
|
|
3334
|
+
failure_message="Failed to add synchronized partition -> '{}'".format(name),
|
|
3335
|
+
)
|
|
3336
|
+
|
|
2244
3337
|
# end of method definition
|
|
2245
3338
|
|
|
2246
3339
|
def add_system_attribute(
|
|
2247
|
-
self,
|
|
3340
|
+
self,
|
|
3341
|
+
name: str,
|
|
3342
|
+
value: str,
|
|
3343
|
+
description: str = "",
|
|
2248
3344
|
) -> dict | None:
|
|
2249
|
-
"""Add a new system attribute to OTDS
|
|
3345
|
+
"""Add a new system attribute to OTDS.
|
|
2250
3346
|
|
|
2251
3347
|
Args:
|
|
2252
|
-
name (str):
|
|
2253
|
-
|
|
2254
|
-
|
|
3348
|
+
name (str):
|
|
3349
|
+
The name of the new system attribute.
|
|
3350
|
+
value (str):
|
|
3351
|
+
The value of the system attribute.
|
|
3352
|
+
description (str, optional):
|
|
3353
|
+
The optional description of the system attribute.
|
|
3354
|
+
|
|
2255
3355
|
Returns:
|
|
2256
|
-
dict
|
|
3356
|
+
dict | None:
|
|
3357
|
+
Request response (dictionary) or None if the REST call fails.
|
|
3358
|
+
|
|
2257
3359
|
"""
|
|
2258
3360
|
|
|
2259
3361
|
system_attribute_post_body_json = {
|
|
@@ -2265,7 +3367,7 @@ class OTDS:
|
|
|
2265
3367
|
request_url = "{}/system_attributes".format(self.config()["systemConfigUrl"])
|
|
2266
3368
|
|
|
2267
3369
|
if description:
|
|
2268
|
-
logger.debug(
|
|
3370
|
+
self.logger.debug(
|
|
2269
3371
|
"Add system attribute -> '%s' ('%s') with value -> %s; calling -> %s",
|
|
2270
3372
|
name,
|
|
2271
3373
|
description,
|
|
@@ -2273,7 +3375,7 @@ class OTDS:
|
|
|
2273
3375
|
request_url,
|
|
2274
3376
|
)
|
|
2275
3377
|
else:
|
|
2276
|
-
logger.debug(
|
|
3378
|
+
self.logger.debug(
|
|
2277
3379
|
"Add system attribute -> '%s' with value -> %s; calling -> %s",
|
|
2278
3380
|
name,
|
|
2279
3381
|
value,
|
|
@@ -2286,24 +3388,28 @@ class OTDS:
|
|
|
2286
3388
|
json_data=system_attribute_post_body_json,
|
|
2287
3389
|
timeout=None,
|
|
2288
3390
|
failure_message="Failed to add system attribute -> '{}' with value -> '{}'".format(
|
|
2289
|
-
name,
|
|
3391
|
+
name,
|
|
3392
|
+
value,
|
|
2290
3393
|
),
|
|
2291
3394
|
)
|
|
2292
3395
|
|
|
2293
3396
|
# end method definition
|
|
2294
3397
|
|
|
2295
3398
|
def get_trusted_sites(self) -> dict | None:
|
|
2296
|
-
"""Get all configured OTDS trusted sites
|
|
3399
|
+
"""Get all configured OTDS trusted sites.
|
|
2297
3400
|
|
|
2298
3401
|
Args:
|
|
2299
3402
|
None
|
|
3403
|
+
|
|
2300
3404
|
Returns:
|
|
2301
|
-
dict
|
|
3405
|
+
dict | None:
|
|
3406
|
+
Request response or None if the REST call fails.
|
|
3407
|
+
|
|
2302
3408
|
"""
|
|
2303
3409
|
|
|
2304
3410
|
request_url = "{}/whitelist".format(self.config()["systemConfigUrl"])
|
|
2305
3411
|
|
|
2306
|
-
logger.debug("Get trusted sites; calling -> %s", request_url)
|
|
3412
|
+
self.logger.debug("Get trusted sites; calling -> %s", request_url)
|
|
2307
3413
|
|
|
2308
3414
|
return self.do_request(
|
|
2309
3415
|
url=request_url,
|
|
@@ -2315,12 +3421,16 @@ class OTDS:
|
|
|
2315
3421
|
# end method definition
|
|
2316
3422
|
|
|
2317
3423
|
def add_trusted_site(self, trusted_site: str) -> dict | None:
|
|
2318
|
-
"""Add a new OTDS trusted site
|
|
3424
|
+
"""Add a new OTDS trusted site.
|
|
2319
3425
|
|
|
2320
3426
|
Args:
|
|
2321
|
-
trusted_site (str):
|
|
2322
|
-
|
|
2323
|
-
|
|
3427
|
+
trusted_site (str):
|
|
3428
|
+
The name of the new trusted site.
|
|
3429
|
+
|
|
3430
|
+
Returns:
|
|
3431
|
+
dict | None:
|
|
3432
|
+
Request response or None if the REST call fails.
|
|
3433
|
+
|
|
2324
3434
|
"""
|
|
2325
3435
|
|
|
2326
3436
|
trusted_site_post_body_json = {"stringList": [trusted_site]}
|
|
@@ -2331,13 +3441,15 @@ class OTDS:
|
|
|
2331
3441
|
|
|
2332
3442
|
if existing_trusted_sites:
|
|
2333
3443
|
trusted_site_post_body_json["stringList"].extend(
|
|
2334
|
-
existing_trusted_sites["stringList"]
|
|
3444
|
+
existing_trusted_sites["stringList"],
|
|
2335
3445
|
)
|
|
2336
3446
|
|
|
2337
3447
|
request_url = "{}/whitelist".format(self.config()["systemConfigUrl"])
|
|
2338
3448
|
|
|
2339
|
-
logger.debug(
|
|
2340
|
-
"Add trusted site -> '%s'; calling -> %s",
|
|
3449
|
+
self.logger.debug(
|
|
3450
|
+
"Add trusted site -> '%s'; calling -> %s",
|
|
3451
|
+
trusted_site,
|
|
3452
|
+
request_url,
|
|
2341
3453
|
)
|
|
2342
3454
|
|
|
2343
3455
|
response = self.do_request(
|
|
@@ -2349,27 +3461,38 @@ class OTDS:
|
|
|
2349
3461
|
parse_request_response=False, # don't parse it!
|
|
2350
3462
|
)
|
|
2351
3463
|
|
|
2352
|
-
if not response.ok:
|
|
3464
|
+
if not response or not response.ok:
|
|
2353
3465
|
return None
|
|
2354
3466
|
|
|
2355
3467
|
return response
|
|
2356
3468
|
|
|
2357
3469
|
# end method definition
|
|
2358
3470
|
|
|
2359
|
-
def enable_audit(
|
|
2360
|
-
|
|
3471
|
+
def enable_audit(
|
|
3472
|
+
self,
|
|
3473
|
+
enable: bool = True,
|
|
3474
|
+
days_to_keep: int = 7,
|
|
3475
|
+
event_types: list | None = None,
|
|
3476
|
+
) -> dict | None:
|
|
3477
|
+
"""Enable the OTDS Audit.
|
|
2361
3478
|
|
|
2362
3479
|
Args:
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
3480
|
+
enable (bool, optional):
|
|
3481
|
+
True = enable audit, False = disable audit.
|
|
3482
|
+
days_to_keep (int, optional):
|
|
3483
|
+
Days to keep the audit information. Default = 7 Days.
|
|
3484
|
+
event_types (list, optional):
|
|
3485
|
+
A list of event types to record.
|
|
3486
|
+
If None then a default list will be used.
|
|
3487
|
+
|
|
3488
|
+
Returns:
|
|
3489
|
+
dict | None:
|
|
3490
|
+
Request response or None if the REST call fails.
|
|
3491
|
+
|
|
2366
3492
|
"""
|
|
2367
3493
|
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
"enabled": "true",
|
|
2371
|
-
"auditTo": "DATABASE",
|
|
2372
|
-
"eventIDs": [
|
|
3494
|
+
if event_types is None:
|
|
3495
|
+
event_types = [
|
|
2373
3496
|
"User Create",
|
|
2374
3497
|
"Group Create",
|
|
2375
3498
|
"User Delete",
|
|
@@ -2417,19 +3540,30 @@ class OTDS:
|
|
|
2417
3540
|
"Tenant Delete",
|
|
2418
3541
|
"Tenant Modify",
|
|
2419
3542
|
"Migration",
|
|
2420
|
-
]
|
|
3543
|
+
]
|
|
3544
|
+
|
|
3545
|
+
audit_put_body_json = {
|
|
3546
|
+
"daysToKeep": str(days_to_keep),
|
|
3547
|
+
"enabled": enable,
|
|
3548
|
+
"auditTo": "DATABASE",
|
|
3549
|
+
"eventIDs": event_types,
|
|
2421
3550
|
}
|
|
2422
3551
|
|
|
2423
3552
|
request_url = "{}/audit".format(self.config()["systemConfigUrl"])
|
|
2424
3553
|
|
|
2425
|
-
|
|
3554
|
+
if enable:
|
|
3555
|
+
self.logger.debug("Enable audit; calling -> %s", request_url)
|
|
3556
|
+
failure_message = "Failed to enable audit"
|
|
3557
|
+
else:
|
|
3558
|
+
self.logger.debug("Disable audit; calling -> %s", request_url)
|
|
3559
|
+
failure_message = "Failed to disable audit"
|
|
2426
3560
|
|
|
2427
3561
|
return self.do_request(
|
|
2428
3562
|
url=request_url,
|
|
2429
3563
|
method="PUT",
|
|
2430
3564
|
json_data=audit_put_body_json,
|
|
2431
3565
|
timeout=None,
|
|
2432
|
-
failure_message=
|
|
3566
|
+
failure_message=failure_message,
|
|
2433
3567
|
parse_request_response=False,
|
|
2434
3568
|
)
|
|
2435
3569
|
|
|
@@ -2447,21 +3581,33 @@ class OTDS:
|
|
|
2447
3581
|
default_scopes: list | None = None, # in OTDS UI: Default scopes
|
|
2448
3582
|
secret: str = "",
|
|
2449
3583
|
) -> dict | None:
|
|
2450
|
-
"""Add a new OAuth client to OTDS
|
|
3584
|
+
"""Add a new OAuth client to OTDS.
|
|
2451
3585
|
|
|
2452
3586
|
Args:
|
|
2453
|
-
client_id (str):
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
3587
|
+
client_id (str):
|
|
3588
|
+
The name of the new OAuth client (should not have blanks).
|
|
3589
|
+
description (str):
|
|
3590
|
+
The description of the OAuth client.
|
|
3591
|
+
redirect_urls (list):
|
|
3592
|
+
A list of redirect URLs (strings).
|
|
3593
|
+
allow_impersonation (bool, optional):
|
|
3594
|
+
Whether or not to allow impersonation.
|
|
3595
|
+
confidential (bool, optional):
|
|
3596
|
+
is confidential
|
|
3597
|
+
auth_scopes (list, optional):
|
|
3598
|
+
The authorization scope. If empty then "Global" is assumed.
|
|
3599
|
+
allowed_scopes (list, optional):
|
|
3600
|
+
In the OTDS UI this is called Permissible scopes.
|
|
3601
|
+
default_scopes (list, optional):
|
|
3602
|
+
In the OTDS UI this is called Default scopes.
|
|
3603
|
+
secret (str, optional):
|
|
3604
|
+
Predefined OAuth client secret. If empty a new secret is generated.
|
|
3605
|
+
|
|
2462
3606
|
Returns:
|
|
2463
|
-
dict
|
|
2464
|
-
|
|
3607
|
+
dict | None:
|
|
3608
|
+
Request response or None if the creation fails.
|
|
3609
|
+
|
|
3610
|
+
Example:
|
|
2465
3611
|
{
|
|
2466
3612
|
"description": "string",
|
|
2467
3613
|
"redirectURLs": [
|
|
@@ -2503,6 +3649,7 @@ class OTDS:
|
|
|
2503
3649
|
"urlId": "string",
|
|
2504
3650
|
"urlLocation": "string"
|
|
2505
3651
|
}
|
|
3652
|
+
|
|
2506
3653
|
"""
|
|
2507
3654
|
|
|
2508
3655
|
# Avoid linter warning W0102:
|
|
@@ -2537,7 +3684,7 @@ class OTDS:
|
|
|
2537
3684
|
|
|
2538
3685
|
request_url = self.oauth_client_url()
|
|
2539
3686
|
|
|
2540
|
-
logger.debug(
|
|
3687
|
+
self.logger.debug(
|
|
2541
3688
|
"Adding oauth client -> '%s' (%s); calling -> %s",
|
|
2542
3689
|
description,
|
|
2543
3690
|
client_id,
|
|
@@ -2555,18 +3702,27 @@ class OTDS:
|
|
|
2555
3702
|
# end method definition
|
|
2556
3703
|
|
|
2557
3704
|
def get_oauth_client(self, client_id: str, show_error: bool = True) -> dict | None:
|
|
2558
|
-
"""Get an existing OAuth client from OTDS
|
|
3705
|
+
"""Get an existing OAuth client from OTDS.
|
|
2559
3706
|
|
|
2560
3707
|
Args:
|
|
2561
|
-
client_id (str):
|
|
2562
|
-
|
|
3708
|
+
client_id (str):
|
|
3709
|
+
The name (= ID) of the OAuth client to retrieve
|
|
3710
|
+
show_error (bool, optional):
|
|
3711
|
+
Whether or not we want to log an error if partion is not found.
|
|
3712
|
+
|
|
2563
3713
|
Returns:
|
|
2564
|
-
dict
|
|
3714
|
+
dict | None:
|
|
3715
|
+
Request response (dictionary) or None if the client is not found.
|
|
3716
|
+
|
|
2565
3717
|
"""
|
|
2566
3718
|
|
|
2567
3719
|
request_url = "{}/{}".format(self.oauth_client_url(), client_id)
|
|
2568
3720
|
|
|
2569
|
-
logger.debug(
|
|
3721
|
+
self.logger.debug(
|
|
3722
|
+
"Get oauth client -> '%s'; calling -> %s",
|
|
3723
|
+
client_id,
|
|
3724
|
+
request_url,
|
|
3725
|
+
)
|
|
2570
3726
|
|
|
2571
3727
|
return self.do_request(
|
|
2572
3728
|
url=request_url,
|
|
@@ -2579,22 +3735,26 @@ class OTDS:
|
|
|
2579
3735
|
# end method definition
|
|
2580
3736
|
|
|
2581
3737
|
def update_oauth_client(self, client_id: str, updates: dict) -> dict | None:
|
|
2582
|
-
"""
|
|
3738
|
+
"""Update an OAuth client with new values.
|
|
2583
3739
|
|
|
2584
3740
|
Args:
|
|
2585
|
-
client_id (str):
|
|
2586
|
-
|
|
2587
|
-
|
|
3741
|
+
client_id (str):
|
|
3742
|
+
The name (= ID) of the OAuth client.
|
|
3743
|
+
updates (dict):
|
|
3744
|
+
New values for OAuth client, e.g.
|
|
3745
|
+
{"description": "this is the new value"}
|
|
2588
3746
|
|
|
2589
3747
|
Returns:
|
|
2590
|
-
dict
|
|
3748
|
+
dict | None:
|
|
3749
|
+
Request response (json) or None if the REST call fails.
|
|
3750
|
+
|
|
2591
3751
|
"""
|
|
2592
3752
|
|
|
2593
3753
|
oauth_client_patch_body_json = updates
|
|
2594
3754
|
|
|
2595
3755
|
request_url = "{}/{}".format(self.oauth_client_url(), client_id)
|
|
2596
3756
|
|
|
2597
|
-
logger.debug(
|
|
3757
|
+
self.logger.debug(
|
|
2598
3758
|
"Update OAuth client -> '%s' with -> %s; calling -> %s",
|
|
2599
3759
|
client_id,
|
|
2600
3760
|
str(updates),
|
|
@@ -2611,19 +3771,25 @@ class OTDS:
|
|
|
2611
3771
|
|
|
2612
3772
|
# end method definition
|
|
2613
3773
|
|
|
2614
|
-
def add_oauth_clients_to_access_role(self, access_role_name: str):
|
|
2615
|
-
"""Add
|
|
3774
|
+
def add_oauth_clients_to_access_role(self, access_role_name: str) -> dict | None:
|
|
3775
|
+
"""Add OAuth clients (in the "OAuthClients" partition) to an OTDS access role.
|
|
2616
3776
|
|
|
2617
3777
|
Args:
|
|
2618
|
-
access_role_name (str):
|
|
3778
|
+
access_role_name (str):
|
|
3779
|
+
The name of the OTDS access role.
|
|
3780
|
+
|
|
2619
3781
|
Returns:
|
|
2620
|
-
|
|
3782
|
+
dict | None:
|
|
3783
|
+
Response of REST call or None in case of an error.
|
|
3784
|
+
|
|
2621
3785
|
"""
|
|
2622
3786
|
|
|
2623
3787
|
request_url = self.config()["accessRoleUrl"] + "/" + access_role_name
|
|
2624
3788
|
|
|
2625
|
-
logger.debug(
|
|
2626
|
-
"Get access role -> '%s'; calling -> %s",
|
|
3789
|
+
self.logger.debug(
|
|
3790
|
+
"Get access role -> '%s'; calling -> %s",
|
|
3791
|
+
access_role_name,
|
|
3792
|
+
request_url,
|
|
2627
3793
|
)
|
|
2628
3794
|
|
|
2629
3795
|
access_role = self.do_request(
|
|
@@ -2631,7 +3797,7 @@ class OTDS:
|
|
|
2631
3797
|
method="GET",
|
|
2632
3798
|
timeout=None,
|
|
2633
3799
|
failure_message="Failed to retrieve access role -> '{}'".format(
|
|
2634
|
-
access_role_name
|
|
3800
|
+
access_role_name,
|
|
2635
3801
|
),
|
|
2636
3802
|
)
|
|
2637
3803
|
if not access_role:
|
|
@@ -2641,7 +3807,7 @@ class OTDS:
|
|
|
2641
3807
|
user_partitions = access_role["accessRoleMembers"]["userPartitions"]
|
|
2642
3808
|
for user_partition in user_partitions:
|
|
2643
3809
|
if user_partition["userPartition"] == "OAuthClients":
|
|
2644
|
-
logger.error(
|
|
3810
|
+
self.logger.error(
|
|
2645
3811
|
"OAuthClients partition already added to role -> %s",
|
|
2646
3812
|
access_role_name,
|
|
2647
3813
|
)
|
|
@@ -2656,7 +3822,7 @@ class OTDS:
|
|
|
2656
3822
|
method="GET",
|
|
2657
3823
|
timeout=None,
|
|
2658
3824
|
failure_message="Failed to get partition info for OAuthClients for role -> '{}'".format(
|
|
2659
|
-
access_role_name
|
|
3825
|
+
access_role_name,
|
|
2660
3826
|
),
|
|
2661
3827
|
)
|
|
2662
3828
|
if not response:
|
|
@@ -2671,7 +3837,7 @@ class OTDS:
|
|
|
2671
3837
|
"userPartition": None,
|
|
2672
3838
|
}
|
|
2673
3839
|
access_role["accessRoleMembers"]["organizationalUnits"].append(
|
|
2674
|
-
oauth_clients_ou_block
|
|
3840
|
+
oauth_clients_ou_block,
|
|
2675
3841
|
)
|
|
2676
3842
|
|
|
2677
3843
|
return self.do_request(
|
|
@@ -2679,7 +3845,7 @@ class OTDS:
|
|
|
2679
3845
|
method="PUT",
|
|
2680
3846
|
timeout=None,
|
|
2681
3847
|
warning_message="Failed to add OAuthClients to access role -> '{}'".format(
|
|
2682
|
-
access_role_name
|
|
3848
|
+
access_role_name,
|
|
2683
3849
|
),
|
|
2684
3850
|
show_error=False,
|
|
2685
3851
|
show_warning=True,
|
|
@@ -2689,21 +3855,24 @@ class OTDS:
|
|
|
2689
3855
|
# end method definition
|
|
2690
3856
|
|
|
2691
3857
|
def get_access_token(self, client_id: str, client_secret: str) -> str | None:
|
|
2692
|
-
"""Get the access token
|
|
3858
|
+
"""Get the access token.
|
|
2693
3859
|
|
|
2694
3860
|
Args:
|
|
2695
|
-
client_id (str):
|
|
2696
|
-
|
|
2697
|
-
|
|
3861
|
+
client_id (str):
|
|
3862
|
+
The OAuth client name (= ID).
|
|
3863
|
+
client_secret (str):
|
|
3864
|
+
The OAuth client secret. This is typically returned
|
|
3865
|
+
by add_oauth_client() method in ["secret"] field.
|
|
2698
3866
|
|
|
2699
3867
|
Returns:
|
|
2700
|
-
str
|
|
3868
|
+
str | None:
|
|
3869
|
+
The access token, or None in case of an error.
|
|
3870
|
+
|
|
2701
3871
|
"""
|
|
2702
3872
|
|
|
2703
3873
|
encoded_client_secret = "{}:{}".format(client_id, client_secret).encode("utf-8")
|
|
2704
|
-
|
|
2705
|
-
"Authorization": "Basic "
|
|
2706
|
-
+ base64.b64encode(encoded_client_secret).decode("utf-8"),
|
|
3874
|
+
access_token_request_headers = {
|
|
3875
|
+
"Authorization": "Basic " + base64.b64encode(encoded_client_secret).decode("utf-8"),
|
|
2707
3876
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
2708
3877
|
}
|
|
2709
3878
|
|
|
@@ -2712,16 +3881,16 @@ class OTDS:
|
|
|
2712
3881
|
response = requests.post(
|
|
2713
3882
|
url=request_url,
|
|
2714
3883
|
data={"grant_type": "client_credentials"},
|
|
2715
|
-
headers=
|
|
2716
|
-
timeout=
|
|
3884
|
+
headers=access_token_request_headers,
|
|
3885
|
+
timeout=REQUEST_TIMEOUT,
|
|
2717
3886
|
)
|
|
2718
3887
|
|
|
2719
3888
|
access_token = None
|
|
2720
3889
|
if response.ok:
|
|
2721
|
-
|
|
3890
|
+
access_token = self.parse_request_response(response)
|
|
2722
3891
|
|
|
2723
|
-
if "access_token" in
|
|
2724
|
-
access_token =
|
|
3892
|
+
if "access_token" in access_token:
|
|
3893
|
+
access_token = access_token["access_token"]
|
|
2725
3894
|
else:
|
|
2726
3895
|
return None
|
|
2727
3896
|
|
|
@@ -2733,12 +3902,17 @@ class OTDS:
|
|
|
2733
3902
|
"""Get the OTDS auth handler with a given name.
|
|
2734
3903
|
|
|
2735
3904
|
Args:
|
|
2736
|
-
name (str):
|
|
3905
|
+
name (str):
|
|
3906
|
+
The name of the authentication handler
|
|
3907
|
+
show_error (bool, optional):
|
|
3908
|
+
Whether or not an error should be logged in case of a failed REST call.
|
|
3909
|
+
If False, then only a warning is logged. Defaults to True.
|
|
2737
3910
|
|
|
2738
3911
|
Returns:
|
|
2739
|
-
dict | None:
|
|
3912
|
+
dict | None:
|
|
3913
|
+
The auth handler dictionary, or None in case of an error.
|
|
2740
3914
|
|
|
2741
|
-
|
|
3915
|
+
Example:
|
|
2742
3916
|
{
|
|
2743
3917
|
'_name': 'Salesforce',
|
|
2744
3918
|
'_id': 'Salesforce',
|
|
@@ -2775,8 +3949,10 @@ class OTDS:
|
|
|
2775
3949
|
|
|
2776
3950
|
request_url = "{}/{}".format(self.auth_handler_url(), name)
|
|
2777
3951
|
|
|
2778
|
-
logger.debug(
|
|
2779
|
-
"Getting authentication handler -> '%s'; calling -> %s",
|
|
3952
|
+
self.logger.debug(
|
|
3953
|
+
"Getting authentication handler -> '%s'; calling -> %s",
|
|
3954
|
+
name,
|
|
3955
|
+
request_url,
|
|
2780
3956
|
)
|
|
2781
3957
|
|
|
2782
3958
|
return self.do_request(
|
|
@@ -2803,26 +3979,40 @@ class OTDS:
|
|
|
2803
3979
|
auth_principal_attributes: list | None = None,
|
|
2804
3980
|
nameid_format: str = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
|
|
2805
3981
|
) -> dict | None:
|
|
2806
|
-
"""Add a new SAML authentication handler
|
|
3982
|
+
"""Add a new SAML authentication handler.
|
|
2807
3983
|
|
|
2808
3984
|
Args:
|
|
2809
|
-
name (str):
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
3985
|
+
name (str):
|
|
3986
|
+
The name of the new authentication handler.
|
|
3987
|
+
description (str):
|
|
3988
|
+
The description of the new authentication handler.
|
|
3989
|
+
scope (str):
|
|
3990
|
+
The name of the user partition (to define a scope of the auth handler)
|
|
3991
|
+
provider_name (str):
|
|
3992
|
+
The description of the new authentication handler.
|
|
3993
|
+
saml_url (str):
|
|
3994
|
+
The SAML URL.
|
|
3995
|
+
otds_sp_endpoint (str):
|
|
3996
|
+
The external(!) service provider URL of OTDS.
|
|
3997
|
+
enabled (bool, optional):
|
|
3998
|
+
Defines if the handler should be enabled or disabled. Default is True = enabled.
|
|
3999
|
+
priority (int, optional):
|
|
4000
|
+
Priority of the Authentical Handler (compared to others). Default is 5
|
|
4001
|
+
active_by_default (bool, optional):
|
|
4002
|
+
Defines whether OTDS should redirect immediately to provider page
|
|
4003
|
+
(not showing the OTDS login at all).
|
|
4004
|
+
auth_principal_attributes (list, optional):
|
|
4005
|
+
List of Authentication principal attributes
|
|
4006
|
+
nameid_format (str, optional):
|
|
4007
|
+
Specifies which NameID format supported by the identity provider
|
|
4008
|
+
contains the desired user identifier. The value in this identifier
|
|
4009
|
+
must correspond to the value of the user attribute specified for the
|
|
4010
|
+
authentication principal attribute.
|
|
4011
|
+
|
|
2824
4012
|
Returns:
|
|
2825
|
-
dict
|
|
4013
|
+
dict | None:
|
|
4014
|
+
Request response (dictionary) or None if the REST call fails.
|
|
4015
|
+
|
|
2826
4016
|
"""
|
|
2827
4017
|
|
|
2828
4018
|
if auth_principal_attributes is None:
|
|
@@ -3110,7 +4300,7 @@ class OTDS:
|
|
|
3110
4300
|
|
|
3111
4301
|
request_url = self.auth_handler_url()
|
|
3112
4302
|
|
|
3113
|
-
logger.debug(
|
|
4303
|
+
self.logger.debug(
|
|
3114
4304
|
"Adding SAML auth handler -> '%s' ('%s'); calling -> %s",
|
|
3115
4305
|
name,
|
|
3116
4306
|
description,
|
|
@@ -3137,23 +4327,33 @@ class OTDS:
|
|
|
3137
4327
|
enabled: bool = True,
|
|
3138
4328
|
priority: int = 10,
|
|
3139
4329
|
auth_principal_attributes: list | None = None,
|
|
3140
|
-
):
|
|
3141
|
-
"""Add a new SAP authentication handler
|
|
4330
|
+
) -> dict | None:
|
|
4331
|
+
"""Add a new SAP authentication handler.
|
|
3142
4332
|
|
|
3143
4333
|
Args:
|
|
3144
|
-
name (str):
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
4334
|
+
name (str):
|
|
4335
|
+
The name of the new authentication handler.
|
|
4336
|
+
description (str):
|
|
4337
|
+
The description of the new authentication handler.
|
|
4338
|
+
scope (str):
|
|
4339
|
+
The name of the user partition (to define a scope of the auth handler)
|
|
4340
|
+
certificate_file (str):
|
|
4341
|
+
A fully qualified file name (with path) to the certificate file.
|
|
4342
|
+
certificate_password (str):
|
|
4343
|
+
The password of the certificate.
|
|
4344
|
+
enabled (bool, optional):
|
|
4345
|
+
Defines if the handler should be enabled or disabled. Default is True = enabled.
|
|
4346
|
+
priority (int, optional):
|
|
4347
|
+
Priority of the Authentical Handler (compared to others). Default is 10.
|
|
4348
|
+
auth_principal_attributes (list, optional):
|
|
4349
|
+
List of Authentication principal attributes.
|
|
4350
|
+
|
|
3152
4351
|
Returns:
|
|
3153
|
-
Request response (json) or None if the REST call fails.
|
|
4352
|
+
dict | None: Request response (json) or None if the REST call fails.
|
|
4353
|
+
|
|
3154
4354
|
"""
|
|
3155
4355
|
|
|
3156
|
-
# Avoid linter warning W0102:
|
|
4356
|
+
# Avoid linter warning W0102 by establishing the default value inside the method:
|
|
3157
4357
|
if auth_principal_attributes is None:
|
|
3158
4358
|
auth_principal_attributes = ["oTExternalID1"]
|
|
3159
4359
|
|
|
@@ -3176,7 +4376,7 @@ class OTDS:
|
|
|
3176
4376
|
"_fileName": False,
|
|
3177
4377
|
"_fileExtensions": None,
|
|
3178
4378
|
"_value": os.path.basename(
|
|
3179
|
-
certificate_file
|
|
4379
|
+
certificate_file,
|
|
3180
4380
|
), # "TM6_Sandbox.pse" - file name only
|
|
3181
4381
|
"_allowedValues": None,
|
|
3182
4382
|
"_confidential": False,
|
|
@@ -3214,7 +4414,7 @@ class OTDS:
|
|
|
3214
4414
|
# 2. Create the auth handler in OTDS
|
|
3215
4415
|
request_url = self.auth_handler_url()
|
|
3216
4416
|
|
|
3217
|
-
logger.debug(
|
|
4417
|
+
self.logger.debug(
|
|
3218
4418
|
"Adding SAP auth handler -> '%s' ('%s'); calling -> %s",
|
|
3219
4419
|
name,
|
|
3220
4420
|
description,
|
|
@@ -3235,21 +4435,21 @@ class OTDS:
|
|
|
3235
4435
|
# 3. Upload the certificate file:
|
|
3236
4436
|
|
|
3237
4437
|
# Check that the certificate (PSE) file is readable:
|
|
3238
|
-
logger.debug("Reading certificate file -> '%s'...", certificate_file)
|
|
4438
|
+
self.logger.debug("Reading certificate file -> '%s'...", certificate_file)
|
|
3239
4439
|
try:
|
|
3240
4440
|
# PSE files are binary - so we need to open with "rb":
|
|
3241
4441
|
with open(certificate_file, "rb") as cert_file:
|
|
3242
4442
|
cert_content = cert_file.read()
|
|
3243
4443
|
if not cert_content:
|
|
3244
|
-
logger.error(
|
|
3245
|
-
"No data in certificate file -> '%s'",
|
|
4444
|
+
self.logger.error(
|
|
4445
|
+
"No data in certificate file -> '%s'",
|
|
4446
|
+
certificate_file,
|
|
3246
4447
|
)
|
|
3247
4448
|
return None
|
|
3248
|
-
except
|
|
3249
|
-
logger.error(
|
|
3250
|
-
"Unable to open certificate file -> '%s'
|
|
4449
|
+
except OSError:
|
|
4450
|
+
self.logger.error(
|
|
4451
|
+
"Unable to open certificate file -> '%s'!",
|
|
3251
4452
|
certificate_file,
|
|
3252
|
-
exception.strerror,
|
|
3253
4453
|
)
|
|
3254
4454
|
return None
|
|
3255
4455
|
|
|
@@ -3257,56 +4457,49 @@ class OTDS:
|
|
|
3257
4457
|
# base64 encoded we will decode it and write it back into the same file
|
|
3258
4458
|
try:
|
|
3259
4459
|
# If file is not base64 encoded the next statement will throw an exception
|
|
3260
|
-
# (this is good)
|
|
3261
4460
|
cert_content_decoded = base64.b64decode(cert_content, validate=True)
|
|
3262
4461
|
cert_content_encoded = base64.b64encode(cert_content_decoded).decode(
|
|
3263
|
-
"utf-8"
|
|
4462
|
+
"utf-8",
|
|
3264
4463
|
)
|
|
3265
4464
|
if cert_content_encoded == cert_content.decode("utf-8"):
|
|
3266
|
-
logger.debug(
|
|
3267
|
-
"Certificate file -> '%s' is base64 encoded",
|
|
4465
|
+
self.logger.debug(
|
|
4466
|
+
"Certificate file -> '%s' is base64 encoded",
|
|
4467
|
+
certificate_file,
|
|
3268
4468
|
)
|
|
3269
4469
|
cert_file_encoded = True
|
|
3270
4470
|
else:
|
|
3271
4471
|
cert_file_encoded = False
|
|
3272
4472
|
except TypeError:
|
|
3273
|
-
logger.debug(
|
|
3274
|
-
"Certificate file -> '%s' is not base64 encoded",
|
|
4473
|
+
self.logger.debug(
|
|
4474
|
+
"Certificate file -> '%s' is not base64 encoded",
|
|
4475
|
+
certificate_file,
|
|
3275
4476
|
)
|
|
3276
4477
|
cert_file_encoded = False
|
|
3277
4478
|
|
|
3278
4479
|
if cert_file_encoded:
|
|
3279
|
-
certificate_file =
|
|
3280
|
-
logger.debug(
|
|
4480
|
+
certificate_file = os.path.join(tempfile.gettempdir(), os.path.basename(certificate_file))
|
|
4481
|
+
self.logger.debug(
|
|
4482
|
+
"Writing decoded certificate file -> %s...",
|
|
4483
|
+
certificate_file,
|
|
4484
|
+
)
|
|
3281
4485
|
try:
|
|
3282
4486
|
# PSE files need to be binary - so we need to open with "wb":
|
|
3283
4487
|
with open(certificate_file, "wb") as cert_file:
|
|
3284
4488
|
cert_file.write(base64.b64decode(cert_content))
|
|
3285
|
-
except
|
|
3286
|
-
logger.error(
|
|
3287
|
-
"Failed writing to file -> '%s'
|
|
4489
|
+
except OSError:
|
|
4490
|
+
self.logger.error(
|
|
4491
|
+
"Failed writing to file -> '%s'!",
|
|
3288
4492
|
certificate_file,
|
|
3289
|
-
exception.strerror,
|
|
3290
4493
|
)
|
|
3291
4494
|
return None
|
|
3292
4495
|
|
|
3293
4496
|
auth_handler_post_data = {
|
|
3294
|
-
"file1_property": "com.opentext.otds.as.drivers.sapssoext.certificate1"
|
|
3295
|
-
}
|
|
3296
|
-
|
|
3297
|
-
# It is important to send the file pointer and not the actual file content
|
|
3298
|
-
# otherwise the file is send base64 encoded which we don't want:
|
|
3299
|
-
auth_handler_post_files = {
|
|
3300
|
-
"file1": (
|
|
3301
|
-
os.path.basename(certificate_file),
|
|
3302
|
-
open(certificate_file, "rb"),
|
|
3303
|
-
"application/octet-stream",
|
|
3304
|
-
)
|
|
4497
|
+
"file1_property": "com.opentext.otds.as.drivers.sapssoext.certificate1",
|
|
3305
4498
|
}
|
|
3306
4499
|
|
|
3307
4500
|
request_url = self.auth_handler_url() + "/" + name + "/files"
|
|
3308
4501
|
|
|
3309
|
-
logger.debug(
|
|
4502
|
+
self.logger.debug(
|
|
3310
4503
|
"Uploading certificate file -> '%s' for SAP auth handler -> '%s' ('%s'); calling -> %s",
|
|
3311
4504
|
certificate_file,
|
|
3312
4505
|
name,
|
|
@@ -3314,18 +4507,30 @@ class OTDS:
|
|
|
3314
4507
|
request_url,
|
|
3315
4508
|
)
|
|
3316
4509
|
|
|
3317
|
-
#
|
|
3318
|
-
#
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
4510
|
+
# It is important to send the file pointer and not the actual file content
|
|
4511
|
+
# otherwise the file is sent base64 encoded, which we don't want:
|
|
4512
|
+
with open(certificate_file, "rb") as file_obj:
|
|
4513
|
+
auth_handler_post_files = {
|
|
4514
|
+
"file1": (
|
|
4515
|
+
os.path.basename(certificate_file),
|
|
4516
|
+
file_obj,
|
|
4517
|
+
"application/octet-stream",
|
|
4518
|
+
),
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4521
|
+
# It is important to NOT pass the headers parameter here!
|
|
4522
|
+
# Basically, if you specify a files parameter (a dictionary),
|
|
4523
|
+
# then requests will send a multipart/form-data POST automatically:
|
|
4524
|
+
response = requests.post(
|
|
4525
|
+
url=request_url,
|
|
4526
|
+
data=auth_handler_post_data,
|
|
4527
|
+
files=auth_handler_post_files,
|
|
4528
|
+
cookies=self.cookie(),
|
|
4529
|
+
timeout=REQUEST_TIMEOUT,
|
|
4530
|
+
)
|
|
4531
|
+
|
|
3327
4532
|
if not response.ok:
|
|
3328
|
-
logger.error(
|
|
4533
|
+
self.logger.error(
|
|
3329
4534
|
"Failed to upload certificate file -> '%s' for SAP auth handler -> '%s'; error -> %s (%s)",
|
|
3330
4535
|
certificate_file,
|
|
3331
4536
|
name,
|
|
@@ -3354,28 +4559,44 @@ class OTDS:
|
|
|
3354
4559
|
priority: int = 10,
|
|
3355
4560
|
auth_principal_attributes: list | None = None,
|
|
3356
4561
|
) -> dict | None:
|
|
3357
|
-
"""Add a new OAuth authentication handler
|
|
3358
|
-
|
|
3359
|
-
Args:
|
|
3360
|
-
name (str):
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
4562
|
+
"""Add a new OAuth authentication handler.
|
|
4563
|
+
|
|
4564
|
+
Args:
|
|
4565
|
+
name (str):
|
|
4566
|
+
The name of the new authentication handler.
|
|
4567
|
+
description (str):
|
|
4568
|
+
The description of the new authentication handler.
|
|
4569
|
+
scope (str):
|
|
4570
|
+
The name of the user partition (to define a scope of the auth handler).
|
|
4571
|
+
provider_name (str):
|
|
4572
|
+
The name of the authentication provider. This name is displayed on the login page.
|
|
4573
|
+
client_id (str):
|
|
4574
|
+
The client ID.
|
|
4575
|
+
client_secret (str):
|
|
4576
|
+
The client secret.
|
|
4577
|
+
active_by_default (bool, optional):
|
|
4578
|
+
Defines, whether to activate this handler for any request to the OTDS login page.
|
|
4579
|
+
If True, any login request to the OTDS login page will be redirected to this OAuth provider.
|
|
4580
|
+
If False, the user has to select the provider on the login page.
|
|
4581
|
+
authorization_endpoint (str, optional):
|
|
4582
|
+
The URL to redirect the browser to for authentication.
|
|
4583
|
+
It is used to retrieve the authorization code or an OIDC id_token.
|
|
4584
|
+
token_endpoint (str, optional):
|
|
4585
|
+
The URL from which to retrieve the access token.
|
|
4586
|
+
Not strictly required with OpenID Connect if using the implicit flow.
|
|
4587
|
+
scope_string (str, optional):
|
|
4588
|
+
Space delimited scope values to send. Include 'openid' to use OpenID Connect.
|
|
4589
|
+
enabled (bool, optional):
|
|
4590
|
+
Defines if the handler should be enabled or disabled. Default is True = enabled.
|
|
4591
|
+
priority (int, optional):
|
|
4592
|
+
Priority of the Authentical Handler (compared to others). Default is 5.
|
|
4593
|
+
auth_principal_attributes (list, optional):
|
|
4594
|
+
List of Authentication principal attributes.
|
|
4595
|
+
|
|
4596
|
+
Returns:
|
|
4597
|
+
dict | None:
|
|
4598
|
+
Request response (dictionary) or None if the REST call fails.
|
|
4599
|
+
|
|
3379
4600
|
"""
|
|
3380
4601
|
|
|
3381
4602
|
# Avoid linter warning W0102:
|
|
@@ -3722,7 +4943,7 @@ class OTDS:
|
|
|
3722
4943
|
|
|
3723
4944
|
request_url = self.auth_handler_url()
|
|
3724
4945
|
|
|
3725
|
-
logger.debug(
|
|
4946
|
+
self.logger.debug(
|
|
3726
4947
|
"Adding OAuth auth handler -> '%s' ('%s'); calling -> %s",
|
|
3727
4948
|
name,
|
|
3728
4949
|
description,
|
|
@@ -3740,24 +4961,29 @@ class OTDS:
|
|
|
3740
4961
|
# end method definition
|
|
3741
4962
|
|
|
3742
4963
|
def consolidate(self, resource_name: str) -> bool:
|
|
3743
|
-
"""Consolidate an OTDS resource
|
|
4964
|
+
"""Consolidate an OTDS resource.
|
|
3744
4965
|
|
|
3745
4966
|
Args:
|
|
3746
|
-
resource_name (str):
|
|
4967
|
+
resource_name (str):
|
|
4968
|
+
The name of the resource to be consolidated.
|
|
4969
|
+
|
|
3747
4970
|
Returns:
|
|
3748
|
-
bool:
|
|
4971
|
+
bool:
|
|
4972
|
+
True, if the consolidation succeeded or False if it failed.
|
|
4973
|
+
|
|
3749
4974
|
"""
|
|
3750
4975
|
|
|
3751
4976
|
resource = self.get_resource(resource_name)
|
|
3752
4977
|
if not resource:
|
|
3753
|
-
logger.error(
|
|
3754
|
-
"Resource -> '%s' not found - cannot consolidate",
|
|
4978
|
+
self.logger.error(
|
|
4979
|
+
"Resource -> '%s' not found - cannot consolidate",
|
|
4980
|
+
resource_name,
|
|
3755
4981
|
)
|
|
3756
4982
|
return False
|
|
3757
4983
|
|
|
3758
4984
|
resource_dn = resource["resourceDN"]
|
|
3759
4985
|
if not resource_dn:
|
|
3760
|
-
logger.error("Resource DN is empty - cannot consolidate")
|
|
4986
|
+
self.logger.error("Resource DN is empty - cannot consolidate")
|
|
3761
4987
|
return False
|
|
3762
4988
|
|
|
3763
4989
|
consolidation_post_body_json = {
|
|
@@ -3769,8 +4995,8 @@ class OTDS:
|
|
|
3769
4995
|
|
|
3770
4996
|
request_url = "{}".format(self.consolidation_url())
|
|
3771
4997
|
|
|
3772
|
-
logger.debug(
|
|
3773
|
-
"Consolidation of resource -> %s (%s); calling -> %s",
|
|
4998
|
+
self.logger.debug(
|
|
4999
|
+
"Consolidation of resource -> '%s' (%s); calling -> %s",
|
|
3774
5000
|
resource_name,
|
|
3775
5001
|
resource_dn,
|
|
3776
5002
|
request_url,
|
|
@@ -3782,15 +5008,12 @@ class OTDS:
|
|
|
3782
5008
|
json_data=consolidation_post_body_json,
|
|
3783
5009
|
timeout=None,
|
|
3784
5010
|
failure_message="Failed to consolidate resource -> '{}'".format(
|
|
3785
|
-
resource_name
|
|
5011
|
+
resource_name,
|
|
3786
5012
|
),
|
|
3787
5013
|
parse_request_response=False,
|
|
3788
5014
|
)
|
|
3789
5015
|
|
|
3790
|
-
|
|
3791
|
-
return True
|
|
3792
|
-
|
|
3793
|
-
return False
|
|
5016
|
+
return bool(response and response.ok)
|
|
3794
5017
|
|
|
3795
5018
|
# end method definition
|
|
3796
5019
|
|
|
@@ -3800,15 +5023,20 @@ class OTDS:
|
|
|
3800
5023
|
allow_impersonation: bool = True,
|
|
3801
5024
|
impersonation_list: list | None = None,
|
|
3802
5025
|
) -> bool:
|
|
3803
|
-
"""Configure impersonation for an OTDS resource
|
|
5026
|
+
"""Configure impersonation for an OTDS resource.
|
|
3804
5027
|
|
|
3805
5028
|
Args:
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
5029
|
+
resource_name (str):
|
|
5030
|
+
Name of the resource to configure impersonation for.
|
|
5031
|
+
allow_impersonation (bool, optional):
|
|
5032
|
+
Whether to turn on or off impersonation (default = True)
|
|
5033
|
+
impersonation_list (list, optional):
|
|
5034
|
+
A list of users to restrict it to (default = empty list = all users)
|
|
5035
|
+
|
|
3810
5036
|
Returns:
|
|
3811
|
-
|
|
5037
|
+
bool:
|
|
5038
|
+
True if the impersonation setting succeeded or False if it failed.
|
|
5039
|
+
|
|
3812
5040
|
"""
|
|
3813
5041
|
|
|
3814
5042
|
# Avoid linter warning W0102:
|
|
@@ -3822,7 +5050,7 @@ class OTDS:
|
|
|
3822
5050
|
|
|
3823
5051
|
request_url = "{}/{}/impersonation".format(self.resource_url(), resource_name)
|
|
3824
5052
|
|
|
3825
|
-
logger.debug(
|
|
5053
|
+
self.logger.debug(
|
|
3826
5054
|
"Impersonation settings for resource -> '%s'; calling -> %s",
|
|
3827
5055
|
resource_name,
|
|
3828
5056
|
request_url,
|
|
@@ -3834,15 +5062,12 @@ class OTDS:
|
|
|
3834
5062
|
json_data=impersonation_put_body_json,
|
|
3835
5063
|
timeout=None,
|
|
3836
5064
|
failure_message="Failed to set impersonation for resource -> '{}'".format(
|
|
3837
|
-
resource_name
|
|
5065
|
+
resource_name,
|
|
3838
5066
|
),
|
|
3839
5067
|
parse_request_response=False,
|
|
3840
5068
|
)
|
|
3841
5069
|
|
|
3842
|
-
|
|
3843
|
-
return True
|
|
3844
|
-
|
|
3845
|
-
return False
|
|
5070
|
+
return bool(response and response.ok)
|
|
3846
5071
|
|
|
3847
5072
|
# end method definition
|
|
3848
5073
|
|
|
@@ -3852,17 +5077,23 @@ class OTDS:
|
|
|
3852
5077
|
allow_impersonation: bool = True,
|
|
3853
5078
|
impersonation_list: list | None = None,
|
|
3854
5079
|
) -> bool:
|
|
3855
|
-
"""Configure impersonation for an OTDS OAuth Client
|
|
5080
|
+
"""Configure impersonation for an OTDS OAuth Client.
|
|
3856
5081
|
|
|
3857
5082
|
Args:
|
|
3858
|
-
client_id (str):
|
|
3859
|
-
|
|
3860
|
-
|
|
5083
|
+
client_id (str):
|
|
5084
|
+
The ID of the OAuth Client to configure impersonation for.
|
|
5085
|
+
allow_impersonation (bool | None, optional):
|
|
5086
|
+
Defines whether to turn on or off impersonation (default = True).
|
|
5087
|
+
impersonation_list (list | None, optional):
|
|
5088
|
+
A list of users to restrict it to; (default = empty list = all users).
|
|
5089
|
+
|
|
3861
5090
|
Returns:
|
|
3862
|
-
bool:
|
|
5091
|
+
bool:
|
|
5092
|
+
True if the impersonation setting succeeded or False if it failed.
|
|
5093
|
+
|
|
3863
5094
|
"""
|
|
3864
5095
|
|
|
3865
|
-
# Avoid linter warning W0102:
|
|
5096
|
+
# Avoid linter warning W0102 by establishing the default inside the method:
|
|
3866
5097
|
if impersonation_list is None:
|
|
3867
5098
|
impersonation_list = []
|
|
3868
5099
|
|
|
@@ -3873,7 +5104,7 @@ class OTDS:
|
|
|
3873
5104
|
|
|
3874
5105
|
request_url = "{}/{}/impersonation".format(self.oauth_client_url(), client_id)
|
|
3875
5106
|
|
|
3876
|
-
logger.debug(
|
|
5107
|
+
self.logger.debug(
|
|
3877
5108
|
"Impersonation settings for OAuth Client -> '%s'; calling -> %s",
|
|
3878
5109
|
client_id,
|
|
3879
5110
|
request_url,
|
|
@@ -3885,27 +5116,26 @@ class OTDS:
|
|
|
3885
5116
|
json_data=impersonation_put_body_json,
|
|
3886
5117
|
timeout=None,
|
|
3887
5118
|
failure_message="Failed to set impersonation for OAuth Client -> '{}'".format(
|
|
3888
|
-
client_id
|
|
5119
|
+
client_id,
|
|
3889
5120
|
),
|
|
3890
5121
|
parse_request_response=False,
|
|
3891
5122
|
)
|
|
3892
5123
|
|
|
3893
|
-
|
|
3894
|
-
return True
|
|
3895
|
-
|
|
3896
|
-
return False
|
|
5124
|
+
return bool(response and response.ok)
|
|
3897
5125
|
|
|
3898
5126
|
# end method definition
|
|
3899
5127
|
|
|
3900
|
-
def get_password_policy(self):
|
|
3901
|
-
"""Get the global password policy
|
|
5128
|
+
def get_password_policy(self) -> dict | None:
|
|
5129
|
+
"""Get the global password policy.
|
|
3902
5130
|
|
|
3903
5131
|
Args:
|
|
3904
5132
|
None
|
|
5133
|
+
|
|
3905
5134
|
Returns:
|
|
3906
|
-
dict
|
|
5135
|
+
dict | None:
|
|
5136
|
+
Request response or None if the REST call fails.
|
|
3907
5137
|
|
|
3908
|
-
|
|
5138
|
+
Example:
|
|
3909
5139
|
{
|
|
3910
5140
|
'passwordHistoryMaximumCount': 3,
|
|
3911
5141
|
'daysBeforeNewPasswordMayBeChanged': 1,
|
|
@@ -3924,11 +5154,12 @@ class OTDS:
|
|
|
3924
5154
|
'blockCommonPassword': False
|
|
3925
5155
|
...
|
|
3926
5156
|
}
|
|
5157
|
+
|
|
3927
5158
|
"""
|
|
3928
5159
|
|
|
3929
5160
|
request_url = "{}/passwordpolicy".format(self.config()["systemConfigUrl"])
|
|
3930
5161
|
|
|
3931
|
-
logger.debug("Getting password policy; calling -> %s", request_url)
|
|
5162
|
+
self.logger.debug("Getting password policy; calling -> %s", request_url)
|
|
3932
5163
|
|
|
3933
5164
|
return self.do_request(
|
|
3934
5165
|
url=request_url,
|
|
@@ -3940,13 +5171,14 @@ class OTDS:
|
|
|
3940
5171
|
# end method definition
|
|
3941
5172
|
|
|
3942
5173
|
def update_password_policy(self, update_values: dict) -> bool:
|
|
3943
|
-
"""Update the global password policy
|
|
5174
|
+
"""Update the global password policy.
|
|
3944
5175
|
|
|
3945
5176
|
Args:
|
|
3946
|
-
update_values (dict):
|
|
3947
|
-
|
|
5177
|
+
update_values (dict):
|
|
5178
|
+
New values for selected settings.
|
|
5179
|
+
A value of 0 means the settings is deactivated.
|
|
3948
5180
|
|
|
3949
|
-
|
|
5181
|
+
Example:
|
|
3950
5182
|
{
|
|
3951
5183
|
'passwordHistoryMaximumCount': 3,
|
|
3952
5184
|
'daysBeforeNewPasswordMayBeChanged': 1,
|
|
@@ -3965,15 +5197,17 @@ class OTDS:
|
|
|
3965
5197
|
'blockCommonPassword': False
|
|
3966
5198
|
...
|
|
3967
5199
|
}
|
|
5200
|
+
|
|
3968
5201
|
Returns:
|
|
3969
|
-
bool:
|
|
3970
|
-
|
|
5202
|
+
bool:
|
|
5203
|
+
True if the REST call succeeds, otherwise False. We use a boolean return
|
|
5204
|
+
value as the response of the REST call does not have meaningful content.
|
|
3971
5205
|
|
|
3972
5206
|
"""
|
|
3973
5207
|
|
|
3974
5208
|
request_url = "{}/passwordpolicy".format(self.config()["systemConfigUrl"])
|
|
3975
5209
|
|
|
3976
|
-
logger.debug(
|
|
5210
|
+
self.logger.debug(
|
|
3977
5211
|
"Update password policy with these new values -> %s; calling -> %s",
|
|
3978
5212
|
str(update_values),
|
|
3979
5213
|
request_url,
|
|
@@ -3985,14 +5219,11 @@ class OTDS:
|
|
|
3985
5219
|
json_data=update_values,
|
|
3986
5220
|
timeout=None,
|
|
3987
5221
|
failure_message="Failed to update password policy with values -> {}".format(
|
|
3988
|
-
update_values
|
|
5222
|
+
update_values,
|
|
3989
5223
|
),
|
|
3990
5224
|
parse_request_response=False,
|
|
3991
5225
|
)
|
|
3992
5226
|
|
|
3993
|
-
|
|
3994
|
-
return True
|
|
3995
|
-
|
|
3996
|
-
return False
|
|
5227
|
+
return bool(response and response.ok)
|
|
3997
5228
|
|
|
3998
5229
|
# end method definition
|