pyxecm 1.4__py3-none-any.whl → 1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +5 -0
- pyxecm/avts.py +1065 -0
- pyxecm/coreshare.py +2532 -0
- pyxecm/customizer/__init__.py +4 -0
- pyxecm/customizer/browser_automation.py +164 -54
- pyxecm/customizer/customizer.py +588 -231
- pyxecm/customizer/k8s.py +143 -29
- pyxecm/customizer/m365.py +1434 -1323
- pyxecm/customizer/payload.py +15073 -5933
- pyxecm/customizer/pht.py +926 -0
- pyxecm/customizer/salesforce.py +866 -351
- pyxecm/customizer/sap.py +4 -4
- pyxecm/customizer/servicenow.py +1467 -0
- pyxecm/customizer/successfactors.py +1056 -0
- pyxecm/helper/__init__.py +2 -0
- pyxecm/helper/assoc.py +44 -1
- pyxecm/helper/data.py +1731 -0
- pyxecm/helper/web.py +170 -46
- pyxecm/helper/xml.py +170 -34
- pyxecm/otac.py +309 -23
- pyxecm/otawp.py +1810 -0
- pyxecm/otcs.py +5308 -2985
- pyxecm/otds.py +1909 -1954
- pyxecm/otmm.py +928 -0
- pyxecm/otpd.py +13 -10
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/METADATA +5 -1
- pyxecm-1.6.dist-info/RECORD +32 -0
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/WHEEL +1 -1
- pyxecm-1.4.dist-info/RECORD +0 -24
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/LICENSE +0 -0
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/top_level.txt +0 -0
pyxecm/coreshare.py
ADDED
|
@@ -0,0 +1,2532 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CoreShare Module to interact with the Core Share API
|
|
3
|
+
See: https://confluence.opentext.com/pages/viewpage.action?spaceKey=OTC&title=APIs+Consumption+based+on+roles
|
|
4
|
+
See also: https://swagger.otxlab.net/ui/?branch=master&yaml=application-specific/core/core-api.yaml
|
|
5
|
+
|
|
6
|
+
Authentication - get Client Secrets:
|
|
7
|
+
1. Login to Core Share as a Tenant Admin User .
|
|
8
|
+
2. Navigate to Security P age.
|
|
9
|
+
3. On OAuth Confidential Clients section provide Description and Redirect URLs. It will populate a
|
|
10
|
+
dialog with Client Secret.
|
|
11
|
+
4. Copy Client Secret as it will not be available anywhere once the dialog is closed.
|
|
12
|
+
|
|
13
|
+
Class: CoreShare
|
|
14
|
+
Methods:
|
|
15
|
+
|
|
16
|
+
__init__ : class initializer
|
|
17
|
+
config : Returns config data set
|
|
18
|
+
credentials: Get credentials (username + password)
|
|
19
|
+
set_credentials: Set the credentials for Core Share based on username and password.
|
|
20
|
+
|
|
21
|
+
request_header_admin: Returns the request header used for Application calls
|
|
22
|
+
that require administrator credentials
|
|
23
|
+
request_header_user: Returns the request header used for Application calls
|
|
24
|
+
that require user (non-admin) credentials.
|
|
25
|
+
do_request: call an Core Share REST API in a safe way.
|
|
26
|
+
parse_request_response: Parse the REST API responses and convert
|
|
27
|
+
them to Python dict in a safe way
|
|
28
|
+
lookup_result_value: Lookup a property value based on a provided key / value pair in the response
|
|
29
|
+
properties of a Core Share REST API call
|
|
30
|
+
exist_result_item: Check if an dict item is in the response
|
|
31
|
+
of the Core Share API call
|
|
32
|
+
get_result_value: Check if a defined value (based on a key) is in the Core Share API response
|
|
33
|
+
|
|
34
|
+
authenticate_admin : Authenticates as Admin at Core Share API
|
|
35
|
+
authenticate_user : Authenticates as Service user at Core Share API
|
|
36
|
+
|
|
37
|
+
get_groups: Get Core Share groups.
|
|
38
|
+
add_group: Add a new Core Share group.
|
|
39
|
+
get_group_members: Get Core Share group members.
|
|
40
|
+
add_group_member: Add a Core Share user to a Cire Share group.
|
|
41
|
+
remove_group_member: Remove a Core Share user from a Core Share group.
|
|
42
|
+
get_group_by_id: Get a Core Share group by its ID.
|
|
43
|
+
get_group_by_name: Get Core Share group by its name.
|
|
44
|
+
search_groups: Search Core Share group(s) by name.
|
|
45
|
+
|
|
46
|
+
get_users: Get Core Share users.
|
|
47
|
+
get_user_by_id: Get a Core Share user by its ID.
|
|
48
|
+
get_user_by_name: Get Core Share user by its first and last name.
|
|
49
|
+
search_users: Search Core Share user(s) by name / property.
|
|
50
|
+
add_user: Add a new Core Share user. This requires a Tenent Admin authorization.
|
|
51
|
+
resend_user_invite: Resend the invite for a Core Share user.
|
|
52
|
+
update_user: Update a Core Share user.
|
|
53
|
+
add_user_access_role: Add an access role to a Core Share user.
|
|
54
|
+
remove_user_access_role: Remove an access role from a Core Share user.
|
|
55
|
+
update_user_access_roles: Define the access roles of a Core Share user.
|
|
56
|
+
update_user_password: Update the password of a Core Share user.
|
|
57
|
+
update_user_photo: Update the Core Share user photo.
|
|
58
|
+
|
|
59
|
+
get_folders: Get Core Share folders under a given parent ID.
|
|
60
|
+
unshare_folder: Unshare Core Share folder with a given resource ID.
|
|
61
|
+
delete_folder: Delete Core Share folder with a given resource ID.
|
|
62
|
+
delete_document: Delete Core Share document with a given resource ID.
|
|
63
|
+
leave_share: Remove a Core Share user from a share (i.e. the user leaves the share)
|
|
64
|
+
stop_share: Stop of share of a user.
|
|
65
|
+
cleanup_user_files: Cleanup all files of a user. This handles different types of resources.
|
|
66
|
+
get_group_shares: Get (incoming) shares of a Core Share group.
|
|
67
|
+
revoke_group_share: Revoke sharing of a folder with a group.
|
|
68
|
+
cleanup_group_shares: Cleanup all incoming shares of a group.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
__author__ = "Dr. Marc Diefenbruch"
|
|
72
|
+
__copyright__ = "Copyright 2024, OpenText"
|
|
73
|
+
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
74
|
+
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
75
|
+
__email__ = "mdiefenb@opentext.com"
|
|
76
|
+
|
|
77
|
+
import os
|
|
78
|
+
import json
|
|
79
|
+
import logging
|
|
80
|
+
import time
|
|
81
|
+
|
|
82
|
+
import urllib.parse
|
|
83
|
+
from http import HTTPStatus
|
|
84
|
+
import requests
|
|
85
|
+
|
|
86
|
+
logger = logging.getLogger("pyxecm.customizer.coreshare")
|
|
87
|
+
|
|
88
|
+
REQUEST_LOGIN_HEADERS = {
|
|
89
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
90
|
+
"Accept": "application/json",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
REQUEST_TIMEOUT = 60
|
|
94
|
+
REQUEST_RETRY_DELAY = 20
|
|
95
|
+
REQUEST_MAX_RETRIES = 2
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class CoreShare(object):
|
|
99
|
+
"""Used to retrieve and automate stettings in Core Share."""
|
|
100
|
+
|
|
101
|
+
_config: dict
|
|
102
|
+
_access_token_user = None
|
|
103
|
+
_access_token_admin = None
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
base_url: str,
|
|
108
|
+
sso_url: str,
|
|
109
|
+
client_id: str,
|
|
110
|
+
client_secret: str,
|
|
111
|
+
username: str,
|
|
112
|
+
password: str,
|
|
113
|
+
):
|
|
114
|
+
"""Initialize the CoreShare object
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
base_url (str): base URL of the Core Share tenant
|
|
118
|
+
sso_url (str): Single Sign On URL of the Core Share tenant
|
|
119
|
+
client_id (str): Core Share Client ID
|
|
120
|
+
client_secret (str): Core Share Client Secret
|
|
121
|
+
username (str): admin user name in Core Share
|
|
122
|
+
password (str): admin password in Core Share
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
core_share_config = {}
|
|
126
|
+
|
|
127
|
+
# Store the credentials and parameters in a config dictionary:
|
|
128
|
+
core_share_config["clientId"] = client_id
|
|
129
|
+
core_share_config["clientSecret"] = client_secret
|
|
130
|
+
core_share_config["username"] = username
|
|
131
|
+
core_share_config["password"] = password
|
|
132
|
+
|
|
133
|
+
# Set the Core Share URLs and REST API endpoints:
|
|
134
|
+
core_share_config["baseUrl"] = base_url
|
|
135
|
+
core_share_config["ssoUrl"] = sso_url
|
|
136
|
+
core_share_config["restUrlv1"] = core_share_config["baseUrl"] + "/api/v1"
|
|
137
|
+
core_share_config["restUrlv3"] = core_share_config["baseUrl"] + "/api/v3"
|
|
138
|
+
core_share_config["groupsUrl"] = core_share_config["restUrlv1"] + "/groups"
|
|
139
|
+
core_share_config["usersUrlv1"] = core_share_config["restUrlv1"] + "/users"
|
|
140
|
+
core_share_config["usersUrlv3"] = core_share_config["restUrlv3"] + "/users"
|
|
141
|
+
core_share_config["invitesUrl"] = core_share_config["restUrlv1"] + "/invites"
|
|
142
|
+
core_share_config["foldersUrlv1"] = core_share_config["restUrlv1"] + "/folders"
|
|
143
|
+
core_share_config["foldersUrlv3"] = core_share_config["restUrlv3"] + "/folders"
|
|
144
|
+
core_share_config["documentsUrlv1"] = (
|
|
145
|
+
core_share_config["restUrlv1"] + "/documents"
|
|
146
|
+
)
|
|
147
|
+
core_share_config["documentsUrlv3"] = (
|
|
148
|
+
core_share_config["restUrlv3"] + "/documents"
|
|
149
|
+
)
|
|
150
|
+
core_share_config["searchUrl"] = core_share_config["baseUrl"] + "/search/v1"
|
|
151
|
+
core_share_config["searchUserUrl"] = core_share_config["searchUrl"] + "/user"
|
|
152
|
+
core_share_config["searchGroupUrl"] = (
|
|
153
|
+
core_share_config["searchUrl"] + "/user/group-all"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
core_share_config["sessionsUrl"] = core_share_config["restUrlv1"] + "/sessions"
|
|
157
|
+
core_share_config["tokenUrl"] = (
|
|
158
|
+
core_share_config["ssoUrl"] + "/otdsws/oauth2/token"
|
|
159
|
+
)
|
|
160
|
+
core_share_config["sessionsUrl"] = core_share_config["restUrlv1"] + "/sessions"
|
|
161
|
+
|
|
162
|
+
# Tenant Admin User Authentication information (Session URL):
|
|
163
|
+
core_share_config["authorizationUrlAdmin"] = (
|
|
164
|
+
core_share_config["sessionsUrl"]
|
|
165
|
+
+ "?client={'type':'web'}"
|
|
166
|
+
+ "&email="
|
|
167
|
+
+ urllib.parse.quote(username)
|
|
168
|
+
+ "&password="
|
|
169
|
+
+ urllib.parse.quote(password)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Tenant Service User Authentication information:
|
|
173
|
+
core_share_config["authorizationUrlCredentials"] = (
|
|
174
|
+
core_share_config["tokenUrl"]
|
|
175
|
+
+ "?client_id="
|
|
176
|
+
+ client_id
|
|
177
|
+
+ "&client_secret="
|
|
178
|
+
+ client_secret
|
|
179
|
+
+ "&grant_type=client_credentials"
|
|
180
|
+
)
|
|
181
|
+
core_share_config["authorizationUrlPassword"] = (
|
|
182
|
+
core_share_config["tokenUrl"]
|
|
183
|
+
+ "?client_id="
|
|
184
|
+
+ client_id
|
|
185
|
+
+ "&client_secret="
|
|
186
|
+
+ client_secret
|
|
187
|
+
+ "&grant_type=password"
|
|
188
|
+
+ "&username="
|
|
189
|
+
+ urllib.parse.quote(username)
|
|
190
|
+
+ "&password="
|
|
191
|
+
+ urllib.parse.quote(password)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
self._config = core_share_config
|
|
195
|
+
|
|
196
|
+
# end method definition
|
|
197
|
+
|
|
198
|
+
def config(self) -> dict:
|
|
199
|
+
"""Returns the configuration dictionary
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
dict: Configuration dictionary
|
|
203
|
+
"""
|
|
204
|
+
return self._config
|
|
205
|
+
|
|
206
|
+
# end method definition
|
|
207
|
+
|
|
208
|
+
def credentials(self) -> dict:
|
|
209
|
+
"""Get credentials (username + password)
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
dict: dictionary with username and password
|
|
213
|
+
"""
|
|
214
|
+
return {
|
|
215
|
+
"username": self.config()["username"],
|
|
216
|
+
"password": self.config()["password"],
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# end method definition
|
|
220
|
+
|
|
221
|
+
def set_credentials(self, username: str = "admin", password: str = ""):
|
|
222
|
+
"""Set the credentials for Core Share based on username and password.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
username (str, optional): Username. Defaults to "admin".
|
|
226
|
+
password (str, optional): Password of the user. Defaults to "".
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
logger.info("Change Core Share credentials to user -> %s...", username)
|
|
230
|
+
|
|
231
|
+
self.config()["username"] = username
|
|
232
|
+
self.config()["password"] = password
|
|
233
|
+
|
|
234
|
+
# As the Authorization URLs include username password
|
|
235
|
+
# we have to update them as well:
|
|
236
|
+
self.config()["authorizationUrlAdmin"] = (
|
|
237
|
+
self.config()["sessionsUrl"]
|
|
238
|
+
+ "?client={'type':'web'}"
|
|
239
|
+
+ "&email="
|
|
240
|
+
+ urllib.parse.quote(username)
|
|
241
|
+
+ "&password="
|
|
242
|
+
+ urllib.parse.quote(password)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
self.config()["authorizationUrlPassword"] = (
|
|
246
|
+
self.config()["tokenUrl"]
|
|
247
|
+
+ "?client_id="
|
|
248
|
+
+ self.config()["clientId"]
|
|
249
|
+
+ "&client_secret="
|
|
250
|
+
+ self.config()["clientSecret"]
|
|
251
|
+
+ "&grant_type=password"
|
|
252
|
+
+ "&username="
|
|
253
|
+
+ urllib.parse.quote(username)
|
|
254
|
+
+ "&password="
|
|
255
|
+
+ urllib.parse.quote(password)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# end method definition
|
|
259
|
+
|
|
260
|
+
def request_header_admin(self, content_type: str = "application/json") -> dict:
|
|
261
|
+
"""Returns the request header used for Application calls
|
|
262
|
+
that require administrator credentials.
|
|
263
|
+
Consists of Bearer access token and Content Type
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
content_type (str, optional): content type for the request
|
|
267
|
+
Return:
|
|
268
|
+
dict: request header values
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
request_header = {
|
|
272
|
+
"Authorization": "Bearer {}".format(self._access_token_admin),
|
|
273
|
+
}
|
|
274
|
+
if content_type:
|
|
275
|
+
request_header["Content-Type"] = content_type
|
|
276
|
+
|
|
277
|
+
return request_header
|
|
278
|
+
|
|
279
|
+
# end method definition
|
|
280
|
+
|
|
281
|
+
def request_header_user(self, content_type: str = "application/json") -> dict:
|
|
282
|
+
"""Returns the request header used for Application calls
|
|
283
|
+
that require user (non-admin) credentials.
|
|
284
|
+
Consists of Bearer access token and Content Type
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
content_type (str, optional): content type for the request
|
|
288
|
+
Return:
|
|
289
|
+
dict: request header values
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
request_header = {
|
|
293
|
+
"Authorization": "Bearer {}".format(self._access_token_user),
|
|
294
|
+
}
|
|
295
|
+
if content_type:
|
|
296
|
+
request_header["Content-Type"] = content_type
|
|
297
|
+
|
|
298
|
+
return request_header
|
|
299
|
+
|
|
300
|
+
# end method definition
|
|
301
|
+
|
|
302
|
+
def do_request(
|
|
303
|
+
self,
|
|
304
|
+
url: str,
|
|
305
|
+
method: str = "GET",
|
|
306
|
+
headers: dict | None = None,
|
|
307
|
+
data: dict | None = None,
|
|
308
|
+
json_data: dict | None = None,
|
|
309
|
+
files: dict | None = None,
|
|
310
|
+
timeout: int | None = REQUEST_TIMEOUT,
|
|
311
|
+
show_error: bool = True,
|
|
312
|
+
show_warning: bool = False,
|
|
313
|
+
warning_message: str = "",
|
|
314
|
+
failure_message: str = "",
|
|
315
|
+
success_message: str = "",
|
|
316
|
+
max_retries: int = REQUEST_MAX_RETRIES,
|
|
317
|
+
retry_forever: bool = False,
|
|
318
|
+
parse_request_response: bool = True,
|
|
319
|
+
user_credentials: bool = False,
|
|
320
|
+
verify: bool = True,
|
|
321
|
+
) -> dict | None:
|
|
322
|
+
"""Call an OTDS REST API in a safe way
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
url (str): URL to send the request to.
|
|
326
|
+
method (str, optional): HTTP method (GET, POST, etc.). Defaults to "GET".
|
|
327
|
+
headers (dict | None, optional): Request Headers. Defaults to None.
|
|
328
|
+
data (dict | None, optional): Request payload. Defaults to None
|
|
329
|
+
files (dict | None, optional): Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
330
|
+
file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
331
|
+
timeout (int | None, optional): Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
332
|
+
show_error (bool, optional): Whether or not an error should be logged in case of a failed REST call.
|
|
333
|
+
If False, then only a warning is logged. Defaults to True.
|
|
334
|
+
warning_message (str, optional): Specific warning message. Defaults to "". If not given the error_message will be used.
|
|
335
|
+
failure_message (str, optional): Specific error message. Defaults to "".
|
|
336
|
+
success_message (str, optional): Specific success message. Defaults to "".
|
|
337
|
+
max_retries (int, optional): How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
|
|
338
|
+
retry_forever (bool, optional): Eventually wait forever - without timeout. Defaults to False.
|
|
339
|
+
parse_request_response (bool, optional): should the response.text be interpreted as json and loaded into a dictionary. True is the default.
|
|
340
|
+
user_credentials (bool, optional): defines if admin or user credentials are used for the REST API call. Default = False = admin credentials
|
|
341
|
+
verify (bool, optional): specify whether or not SSL certificates should be verified when making an HTTPS request. Default = True
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
dict | None: Response of OTDS REST API or None in case of an error.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
if headers is None:
|
|
348
|
+
logger.error("Missing request header. Cannot send request to Core Share!")
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
# In case of an expired session we reauthenticate and
|
|
352
|
+
# try 1 more time. Session expiration should not happen
|
|
353
|
+
# twice in a row:
|
|
354
|
+
retries = 0
|
|
355
|
+
|
|
356
|
+
while True:
|
|
357
|
+
try:
|
|
358
|
+
response = requests.request(
|
|
359
|
+
method=method,
|
|
360
|
+
url=url,
|
|
361
|
+
data=data,
|
|
362
|
+
json=json_data,
|
|
363
|
+
files=files,
|
|
364
|
+
headers=headers,
|
|
365
|
+
timeout=timeout,
|
|
366
|
+
verify=verify,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if response.ok:
|
|
370
|
+
if success_message:
|
|
371
|
+
logger.info(success_message)
|
|
372
|
+
if parse_request_response:
|
|
373
|
+
return self.parse_request_response(response)
|
|
374
|
+
else:
|
|
375
|
+
return response
|
|
376
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
377
|
+
elif response.status_code == 401 and retries == 0:
|
|
378
|
+
if user_credentials:
|
|
379
|
+
logger.debug(
|
|
380
|
+
"User session has expired - try to re-authenticate..."
|
|
381
|
+
)
|
|
382
|
+
self.authenticate_user(revalidate=True)
|
|
383
|
+
# Make sure to not change the content type:
|
|
384
|
+
headers = self.request_header_user(
|
|
385
|
+
content_type=headers.get("Content-Type", None)
|
|
386
|
+
)
|
|
387
|
+
else:
|
|
388
|
+
logger.warning(
|
|
389
|
+
"Admin session has expired - try to re-authenticate..."
|
|
390
|
+
)
|
|
391
|
+
self.authenticate_admin(revalidate=True)
|
|
392
|
+
# Make sure to not change the content type:
|
|
393
|
+
headers = self.request_header_admin(
|
|
394
|
+
content_type=headers.get("Content-Type", None)
|
|
395
|
+
)
|
|
396
|
+
retries += 1
|
|
397
|
+
else:
|
|
398
|
+
# Handle plain HTML responses to not pollute the logs
|
|
399
|
+
content_type = response.headers.get("content-type", None)
|
|
400
|
+
if content_type == "text/html":
|
|
401
|
+
response_text = "HTML content (only printed in debug log)"
|
|
402
|
+
elif "image" in content_type:
|
|
403
|
+
response_text = "Image content (not printed)"
|
|
404
|
+
else:
|
|
405
|
+
response_text = response.text
|
|
406
|
+
|
|
407
|
+
if show_error:
|
|
408
|
+
logger.error(
|
|
409
|
+
"%s; status -> %s/%s; error -> %s",
|
|
410
|
+
failure_message,
|
|
411
|
+
response.status_code,
|
|
412
|
+
HTTPStatus(response.status_code).phrase,
|
|
413
|
+
response_text,
|
|
414
|
+
)
|
|
415
|
+
elif show_warning:
|
|
416
|
+
logger.warning(
|
|
417
|
+
"%s; status -> %s/%s; warning -> %s",
|
|
418
|
+
warning_message if warning_message else failure_message,
|
|
419
|
+
response.status_code,
|
|
420
|
+
HTTPStatus(response.status_code).phrase,
|
|
421
|
+
response_text,
|
|
422
|
+
)
|
|
423
|
+
if content_type == "text/html":
|
|
424
|
+
logger.debug(
|
|
425
|
+
"%s; status -> %s/%s; warning -> %s",
|
|
426
|
+
failure_message,
|
|
427
|
+
response.status_code,
|
|
428
|
+
HTTPStatus(response.status_code).phrase,
|
|
429
|
+
response.text,
|
|
430
|
+
)
|
|
431
|
+
return None
|
|
432
|
+
except requests.exceptions.Timeout:
|
|
433
|
+
if retries <= max_retries:
|
|
434
|
+
logger.warning(
|
|
435
|
+
"Request timed out. Retrying in %s seconds...",
|
|
436
|
+
str(REQUEST_RETRY_DELAY),
|
|
437
|
+
)
|
|
438
|
+
retries += 1
|
|
439
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
440
|
+
else:
|
|
441
|
+
logger.error(
|
|
442
|
+
"%s; timeout error",
|
|
443
|
+
failure_message,
|
|
444
|
+
)
|
|
445
|
+
if retry_forever:
|
|
446
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
447
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
448
|
+
timeout = None
|
|
449
|
+
else:
|
|
450
|
+
return None
|
|
451
|
+
except requests.exceptions.ConnectionError:
|
|
452
|
+
if retries <= max_retries:
|
|
453
|
+
logger.warning(
|
|
454
|
+
"Connection error. Retrying in %s seconds...",
|
|
455
|
+
str(REQUEST_RETRY_DELAY),
|
|
456
|
+
)
|
|
457
|
+
retries += 1
|
|
458
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
459
|
+
else:
|
|
460
|
+
logger.error(
|
|
461
|
+
"%s; connection error",
|
|
462
|
+
failure_message,
|
|
463
|
+
)
|
|
464
|
+
if retry_forever:
|
|
465
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
466
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
467
|
+
timeout = None
|
|
468
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
469
|
+
else:
|
|
470
|
+
return None
|
|
471
|
+
# end try
|
|
472
|
+
logger.debug(
|
|
473
|
+
"Retrying REST API %s call -> %s... (retry = %s)",
|
|
474
|
+
method,
|
|
475
|
+
url,
|
|
476
|
+
str(retries),
|
|
477
|
+
)
|
|
478
|
+
# end while True
|
|
479
|
+
|
|
480
|
+
# end method definition
|
|
481
|
+
|
|
482
|
+
def parse_request_response(
|
|
483
|
+
self,
|
|
484
|
+
response_object: requests.Response,
|
|
485
|
+
additional_error_message: str = "",
|
|
486
|
+
show_error: bool = True,
|
|
487
|
+
) -> dict | None:
|
|
488
|
+
"""Converts the request response (JSon) to a Python dict in a safe way
|
|
489
|
+
that also handles exceptions. It first tries to load the response.text
|
|
490
|
+
via json.loads() that produces a dict output. Only if response.text is
|
|
491
|
+
not set or is empty it just converts the response_object to a dict using
|
|
492
|
+
the vars() built-in method.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
response_object (object): this is reponse object delivered by the request call
|
|
496
|
+
additional_error_message (str, optional): use a more specific error message
|
|
497
|
+
in case of an error
|
|
498
|
+
show_error (bool): True: write an error to the log file
|
|
499
|
+
False: write a warning to the log file
|
|
500
|
+
Returns:
|
|
501
|
+
dict: response information or None in case of an error
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
if not response_object:
|
|
505
|
+
return None
|
|
506
|
+
|
|
507
|
+
try:
|
|
508
|
+
if response_object.text:
|
|
509
|
+
dict_object = json.loads(response_object.text)
|
|
510
|
+
else:
|
|
511
|
+
dict_object = vars(response_object)
|
|
512
|
+
except json.JSONDecodeError as exception:
|
|
513
|
+
if additional_error_message:
|
|
514
|
+
message = "Cannot decode response as JSon. {}; error -> {}".format(
|
|
515
|
+
additional_error_message, exception
|
|
516
|
+
)
|
|
517
|
+
else:
|
|
518
|
+
message = "Cannot decode response as JSon; error -> {}".format(
|
|
519
|
+
exception
|
|
520
|
+
)
|
|
521
|
+
if show_error:
|
|
522
|
+
logger.error(message)
|
|
523
|
+
else:
|
|
524
|
+
logger.warning(message)
|
|
525
|
+
return None
|
|
526
|
+
else:
|
|
527
|
+
return dict_object
|
|
528
|
+
|
|
529
|
+
# end method definition
|
|
530
|
+
|
|
531
|
+
def lookup_result_value(
|
|
532
|
+
self, response: dict, key: str, value: str, return_key: str
|
|
533
|
+
) -> str | None:
|
|
534
|
+
"""Lookup a property value based on a provided key / value pair in the
|
|
535
|
+
response properties of an Extended ECM REST API call.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
response (dict): REST response from an OTCS REST Call
|
|
539
|
+
key (str): property name (key)
|
|
540
|
+
value (str): value to find in the item with the matching key
|
|
541
|
+
return_key (str): determines which value to return based on the name of the dict key
|
|
542
|
+
Returns:
|
|
543
|
+
str: value of the property with the key defined in "return_key"
|
|
544
|
+
or None if the lookup fails
|
|
545
|
+
"""
|
|
546
|
+
|
|
547
|
+
if not response:
|
|
548
|
+
return None
|
|
549
|
+
if not "results" in response:
|
|
550
|
+
return None
|
|
551
|
+
|
|
552
|
+
results = response["results"]
|
|
553
|
+
|
|
554
|
+
if not results or not isinstance(results, list):
|
|
555
|
+
return None
|
|
556
|
+
|
|
557
|
+
for result in results:
|
|
558
|
+
if key in result and result[key] == value and return_key in result:
|
|
559
|
+
return result[return_key]
|
|
560
|
+
return None
|
|
561
|
+
|
|
562
|
+
# end method definition
|
|
563
|
+
|
|
564
|
+
def exist_result_item(
|
|
565
|
+
self, response: dict, key: str, value: str, results_marker: str = "results"
|
|
566
|
+
) -> bool:
|
|
567
|
+
"""Check existence of key / value pair in the response properties of a Core Share API call.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
response (dict): REST response from a Core Share API call
|
|
571
|
+
key (str): property name (key)
|
|
572
|
+
value (str): value to find in the item with the matching key
|
|
573
|
+
Returns:
|
|
574
|
+
bool: True if the value was found, False otherwise
|
|
575
|
+
"""
|
|
576
|
+
|
|
577
|
+
if not response:
|
|
578
|
+
return False
|
|
579
|
+
|
|
580
|
+
if results_marker in response:
|
|
581
|
+
results = response[results_marker]
|
|
582
|
+
if not results or not isinstance(results, list):
|
|
583
|
+
return False
|
|
584
|
+
|
|
585
|
+
for result in results:
|
|
586
|
+
if value == result[key]:
|
|
587
|
+
return True
|
|
588
|
+
else:
|
|
589
|
+
if not key in response:
|
|
590
|
+
return False
|
|
591
|
+
if value == response[key]:
|
|
592
|
+
return True
|
|
593
|
+
|
|
594
|
+
return False
|
|
595
|
+
|
|
596
|
+
# end method definition
|
|
597
|
+
|
|
598
|
+
def get_result_value(
|
|
599
|
+
self,
|
|
600
|
+
response: dict | list,
|
|
601
|
+
key: str,
|
|
602
|
+
index: int = 0,
|
|
603
|
+
) -> str | None:
|
|
604
|
+
"""Get value of a result property with a given key of a Core Share API call.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
response (dict or list): REST response from a Core Share REST Call
|
|
608
|
+
key (str): property name (key)
|
|
609
|
+
index (int, optional): Index to use (1st element has index 0).
|
|
610
|
+
Defaults to 0.
|
|
611
|
+
Returns:
|
|
612
|
+
str: value for the key, None otherwise
|
|
613
|
+
"""
|
|
614
|
+
|
|
615
|
+
if not response:
|
|
616
|
+
return None
|
|
617
|
+
|
|
618
|
+
# response is mostly a dictionary but in some cases also a list (e.g. add_group_member())
|
|
619
|
+
if isinstance(response, list):
|
|
620
|
+
if len(response) - 1 < index:
|
|
621
|
+
return None
|
|
622
|
+
if not key in response[index]:
|
|
623
|
+
return None
|
|
624
|
+
value = response[index][key]
|
|
625
|
+
return value
|
|
626
|
+
|
|
627
|
+
if isinstance(response, dict):
|
|
628
|
+
# Does response have a "results" substructure?
|
|
629
|
+
if "results" in response:
|
|
630
|
+
# we expect results to be a list!
|
|
631
|
+
values = response["results"]
|
|
632
|
+
if (
|
|
633
|
+
not values
|
|
634
|
+
or not isinstance(values, list)
|
|
635
|
+
or len(values) - 1 < index
|
|
636
|
+
):
|
|
637
|
+
return None
|
|
638
|
+
if not key in values[index]:
|
|
639
|
+
return None
|
|
640
|
+
value = values[index][key]
|
|
641
|
+
else: # simple response as dictionary - try to find key in response directly:
|
|
642
|
+
if not key in response:
|
|
643
|
+
return None
|
|
644
|
+
value = response[key]
|
|
645
|
+
|
|
646
|
+
return value
|
|
647
|
+
|
|
648
|
+
return None
|
|
649
|
+
|
|
650
|
+
# end method definition
|
|
651
|
+
|
|
652
|
+
def authenticate_admin(
|
|
653
|
+
self,
|
|
654
|
+
revalidate: bool = False,
|
|
655
|
+
) -> str | None:
|
|
656
|
+
"""Authenticate at Core Share as Tenant Admin.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
revalidate (bool, optional): determinse if a re-athentication is enforced
|
|
660
|
+
(e.g. if session has timed out with 401 error)
|
|
661
|
+
Returns:
|
|
662
|
+
str: Access token. Also stores access token in self._access_token. None in case of error
|
|
663
|
+
"""
|
|
664
|
+
|
|
665
|
+
# Already authenticated and session still valid?
|
|
666
|
+
if self._access_token_admin and not revalidate:
|
|
667
|
+
logger.debug(
|
|
668
|
+
"Session still valid - return existing access token -> %s",
|
|
669
|
+
str(self._access_token_admin),
|
|
670
|
+
)
|
|
671
|
+
return self._access_token_admin
|
|
672
|
+
|
|
673
|
+
request_url = self.config()["authorizationUrlAdmin"]
|
|
674
|
+
|
|
675
|
+
request_header = REQUEST_LOGIN_HEADERS
|
|
676
|
+
|
|
677
|
+
logger.debug("Requesting Core Share Admin Access Token from -> %s", request_url)
|
|
678
|
+
|
|
679
|
+
response = None
|
|
680
|
+
self._access_token_admin = None
|
|
681
|
+
|
|
682
|
+
try:
|
|
683
|
+
response = requests.post(
|
|
684
|
+
request_url,
|
|
685
|
+
headers=request_header,
|
|
686
|
+
timeout=REQUEST_TIMEOUT,
|
|
687
|
+
)
|
|
688
|
+
except requests.exceptions.ConnectionError as exception:
|
|
689
|
+
logger.warning(
|
|
690
|
+
"Unable to connect to -> %s : %s",
|
|
691
|
+
request_url,
|
|
692
|
+
exception,
|
|
693
|
+
)
|
|
694
|
+
return None
|
|
695
|
+
|
|
696
|
+
if response.ok:
|
|
697
|
+
authenticate_dict = self.parse_request_response(response)
|
|
698
|
+
if not authenticate_dict:
|
|
699
|
+
return None
|
|
700
|
+
else:
|
|
701
|
+
cookies = response.cookies
|
|
702
|
+
if "AccessToken" in cookies:
|
|
703
|
+
access_token = cookies["AccessToken"]
|
|
704
|
+
|
|
705
|
+
# String manipulation to extract pure AccessToken
|
|
706
|
+
if access_token.startswith("s%3A"):
|
|
707
|
+
access_token = access_token[4:]
|
|
708
|
+
access_token = access_token.rsplit(".", 1)[0]
|
|
709
|
+
|
|
710
|
+
# Store authentication access_token:
|
|
711
|
+
self._access_token_admin = access_token
|
|
712
|
+
logger.debug(
|
|
713
|
+
"Tenant Admin Access Token -> %s", self._access_token_admin
|
|
714
|
+
)
|
|
715
|
+
else:
|
|
716
|
+
return None
|
|
717
|
+
else:
|
|
718
|
+
logger.error(
|
|
719
|
+
"Failed to request a Core Share Tenant Admin Access Token; error -> %s",
|
|
720
|
+
response.text,
|
|
721
|
+
)
|
|
722
|
+
return None
|
|
723
|
+
|
|
724
|
+
return self._access_token_admin
|
|
725
|
+
|
|
726
|
+
# end method definition
|
|
727
|
+
|
|
728
|
+
def authenticate_user(
|
|
729
|
+
self, revalidate: bool = False, grant_type: str = "password"
|
|
730
|
+
) -> str | None:
|
|
731
|
+
"""Authenticate at Core Share as Tenant Service User (TSU) with client ID and client secret.
|
|
732
|
+
|
|
733
|
+
Args:
|
|
734
|
+
revalidate (bool, optional): determinse if a re-athentication is enforced
|
|
735
|
+
(e.g. if session has timed out with 401 error)
|
|
736
|
+
grant_type (str, optional): Can either be "client_credentials" (default) or "password".
|
|
737
|
+
Returns:
|
|
738
|
+
str: Access token. Also stores access token in self._access_token. None in case of error
|
|
739
|
+
"""
|
|
740
|
+
|
|
741
|
+
# Already authenticated and session still valid?
|
|
742
|
+
if self._access_token_user and not revalidate:
|
|
743
|
+
logger.debug(
|
|
744
|
+
"Session still valid - return existing access token -> %s",
|
|
745
|
+
str(self._access_token_user),
|
|
746
|
+
)
|
|
747
|
+
return self._access_token_user
|
|
748
|
+
|
|
749
|
+
if grant_type == "client_credentials":
|
|
750
|
+
request_url = self.config()["authorizationUrlCredentials"]
|
|
751
|
+
elif grant_type == "password":
|
|
752
|
+
request_url = self.config()["authorizationUrlPassword"]
|
|
753
|
+
else:
|
|
754
|
+
logger.error("Illegal grant type - authorization not possible!")
|
|
755
|
+
return None
|
|
756
|
+
|
|
757
|
+
request_header = REQUEST_LOGIN_HEADERS
|
|
758
|
+
|
|
759
|
+
logger.debug(
|
|
760
|
+
"Requesting Core Share Tenant Service User Access Token from -> %s",
|
|
761
|
+
request_url,
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
response = None
|
|
765
|
+
self._access_token_user = None
|
|
766
|
+
|
|
767
|
+
try:
|
|
768
|
+
response = requests.post(
|
|
769
|
+
request_url,
|
|
770
|
+
headers=request_header,
|
|
771
|
+
timeout=REQUEST_TIMEOUT,
|
|
772
|
+
)
|
|
773
|
+
except requests.exceptions.ConnectionError as exception:
|
|
774
|
+
logger.warning(
|
|
775
|
+
"Unable to connect to -> %s : %s",
|
|
776
|
+
request_url,
|
|
777
|
+
exception,
|
|
778
|
+
)
|
|
779
|
+
return None
|
|
780
|
+
|
|
781
|
+
if response.ok:
|
|
782
|
+
authenticate_dict = self.parse_request_response(response)
|
|
783
|
+
if not authenticate_dict:
|
|
784
|
+
return None
|
|
785
|
+
else:
|
|
786
|
+
# Store authentication access_token:
|
|
787
|
+
self._access_token_user = authenticate_dict["access_token"]
|
|
788
|
+
logger.debug(
|
|
789
|
+
"Tenant Service User Access Token -> %s", self._access_token_user
|
|
790
|
+
)
|
|
791
|
+
else:
|
|
792
|
+
logger.error(
|
|
793
|
+
"Failed to request a Core Share Tenant Service User Access Token; error -> %s",
|
|
794
|
+
response.text,
|
|
795
|
+
)
|
|
796
|
+
return None
|
|
797
|
+
|
|
798
|
+
return self._access_token_user
|
|
799
|
+
|
|
800
|
+
# end method definition
|
|
801
|
+
|
|
802
|
+
def get_groups(self, offset: int = 0, count: int = 25) -> dict | None:
|
|
803
|
+
"""Get Core Share groups.
|
|
804
|
+
|
|
805
|
+
Args:
|
|
806
|
+
offset (int, optional): index of first group (for pagination). Defaults to 0.
|
|
807
|
+
count (int, optional): number of groups to return (page length). Defaults to 25.
|
|
808
|
+
|
|
809
|
+
Returns:
|
|
810
|
+
dict | None: Dictionary with the Core Share group data or None if the request fails.
|
|
811
|
+
|
|
812
|
+
Example response:
|
|
813
|
+
{
|
|
814
|
+
'_links': {
|
|
815
|
+
'self': {'href': '/api/v1/groups?offset=undefined&count=25'},
|
|
816
|
+
'next': {'href': '/api/v1/groups?offset=NaN&count=25'}
|
|
817
|
+
},
|
|
818
|
+
'results': [
|
|
819
|
+
{
|
|
820
|
+
'id': '2593534258421173790',
|
|
821
|
+
'type': 'group',
|
|
822
|
+
'tenantId': '2157293035593927996',
|
|
823
|
+
'displayName': 'Innovate',
|
|
824
|
+
'name': 'Innovate',
|
|
825
|
+
'createdAt': '2024-05-01T09:29:36.370Z',
|
|
826
|
+
'uri': '/api/v1/groups/2593534258421173790',
|
|
827
|
+
'imageuri': '/img/app/group-default-lrg.png',
|
|
828
|
+
'thumbnailUri': '/img/app/group-default-sm.png',
|
|
829
|
+
'defaultImageUri': True,
|
|
830
|
+
'description': 'Demo Company Top Level Group',
|
|
831
|
+
'tenantName': 'terrarium'
|
|
832
|
+
}
|
|
833
|
+
]
|
|
834
|
+
}
|
|
835
|
+
"""
|
|
836
|
+
|
|
837
|
+
if not self._access_token_user:
|
|
838
|
+
self.authenticate_user()
|
|
839
|
+
|
|
840
|
+
request_header = self.request_header_user()
|
|
841
|
+
request_url = self.config()["groupsUrl"] + "?offset={}&count={}".format(
|
|
842
|
+
offset, count
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
logger.debug("Get Core Share groups; calling -> %s", request_url)
|
|
846
|
+
|
|
847
|
+
return self.do_request(
|
|
848
|
+
url=request_url,
|
|
849
|
+
method="GET",
|
|
850
|
+
headers=request_header,
|
|
851
|
+
timeout=REQUEST_TIMEOUT,
|
|
852
|
+
failure_message="Failed to get Core Share groups",
|
|
853
|
+
user_credentials=True,
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
# end method definition
|
|
857
|
+
|
|
858
|
+
def add_group(
|
|
859
|
+
self,
|
|
860
|
+
group_name: str,
|
|
861
|
+
description: str = "",
|
|
862
|
+
) -> dict | None:
|
|
863
|
+
"""Add a new Core Share group. This requires a Tenent Admin authorization.
|
|
864
|
+
|
|
865
|
+
Args:
|
|
866
|
+
group_name (str): Name of the new Core Share group
|
|
867
|
+
description (str): Description of the new Core Share group
|
|
868
|
+
|
|
869
|
+
Returns:
|
|
870
|
+
dict | None: Dictionary with the Core Share Group data or None if the request fails.
|
|
871
|
+
|
|
872
|
+
Example response:
|
|
873
|
+
{
|
|
874
|
+
"id": "2593534258421173790",
|
|
875
|
+
"state": "enabled",
|
|
876
|
+
"isEnabled": true,
|
|
877
|
+
"isDeleted": false,
|
|
878
|
+
"uri": "/api/v1/groups/2593534258421173790",
|
|
879
|
+
"description": "Demo Company Top Level Group",
|
|
880
|
+
"name": "Innovate",
|
|
881
|
+
"imageUri": "/img/icons/mimeIcons/mime_group32.svg",
|
|
882
|
+
"thumbnailUri": "/img/icons/mimeIcons/mime_group32.svg",
|
|
883
|
+
"defaultImageUri": true,
|
|
884
|
+
"memberCount": 0,
|
|
885
|
+
"createdAt": "2024-05-01T09:29:36.370Z",
|
|
886
|
+
"type": "group",
|
|
887
|
+
"isSync": false,
|
|
888
|
+
"tenantId": "2157293035593927996"
|
|
889
|
+
}
|
|
890
|
+
"""
|
|
891
|
+
|
|
892
|
+
if not self._access_token_admin:
|
|
893
|
+
self.authenticate_admin()
|
|
894
|
+
|
|
895
|
+
request_header = self.request_header_admin()
|
|
896
|
+
request_url = self.config()["groupsUrl"]
|
|
897
|
+
|
|
898
|
+
payload = {"name": group_name, "description": description}
|
|
899
|
+
|
|
900
|
+
logger.debug(
|
|
901
|
+
"Adding Core Share group -> %s; calling -> %s", group_name, request_url
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
return self.do_request(
|
|
905
|
+
url=request_url,
|
|
906
|
+
method="POST",
|
|
907
|
+
headers=request_header,
|
|
908
|
+
data=json.dumps(payload),
|
|
909
|
+
timeout=REQUEST_TIMEOUT,
|
|
910
|
+
failure_message="Failed to add Core Share group -> '{}'".format(group_name),
|
|
911
|
+
user_credentials=False,
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
# end method definition
|
|
915
|
+
|
|
916
|
+
def get_group_members(self, group_id: str) -> dict | None:
|
|
917
|
+
"""Get Core Share group members.
|
|
918
|
+
|
|
919
|
+
Args:
|
|
920
|
+
group_id (str): ID of the group to deliver the members for.
|
|
921
|
+
|
|
922
|
+
Returns:
|
|
923
|
+
dict | None: Dictionary with the Core Share group membership data or None if the request fails.
|
|
924
|
+
|
|
925
|
+
Example response:
|
|
926
|
+
{
|
|
927
|
+
'groupMembers': [
|
|
928
|
+
{
|
|
929
|
+
'id': '2422700172682204885',
|
|
930
|
+
'type': 'user',
|
|
931
|
+
'tenantId': '2157293035593927996',
|
|
932
|
+
'firstName': 'Andy',
|
|
933
|
+
'lastName': 'Wyatt',
|
|
934
|
+
'displayName': 'Andy Wyatt',
|
|
935
|
+
'title': 'Buyer',
|
|
936
|
+
'company': 'terrarium',
|
|
937
|
+
'email': 'awyatt@M365x41497014.onmicrosoft.com',
|
|
938
|
+
'otSaaSUID': 'f5a6b58e-ad43-4e2d-a3e6-5c0fcd5cd4b1',
|
|
939
|
+
'otSaaSPID': 'aa49f566-0874-41e9-9924-452852ebaf7a',
|
|
940
|
+
'uri': '/api/v1/users/2422700172682204885',
|
|
941
|
+
'imageuri': '/img/app/profile-default-lrg.png',
|
|
942
|
+
'thumbnailUri': '/img/app/topbar-profile-default-sm.png',
|
|
943
|
+
'defaultImageUri': True,
|
|
944
|
+
'isSpecificGroupAdmin': False
|
|
945
|
+
}
|
|
946
|
+
],
|
|
947
|
+
'pending': [
|
|
948
|
+
|
|
949
|
+
],
|
|
950
|
+
'count': 0
|
|
951
|
+
}
|
|
952
|
+
"""
|
|
953
|
+
|
|
954
|
+
if not self._access_token_admin:
|
|
955
|
+
self.authenticate_admin()
|
|
956
|
+
|
|
957
|
+
request_header = self.request_header_admin()
|
|
958
|
+
request_url = self.config()["groupsUrl"] + "/{}".format(group_id) + "/members"
|
|
959
|
+
|
|
960
|
+
logger.debug(
|
|
961
|
+
"Get members for Core Share group with ID -> %s; calling -> %s",
|
|
962
|
+
group_id,
|
|
963
|
+
request_url,
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
return self.do_request(
|
|
967
|
+
url=request_url,
|
|
968
|
+
method="GET",
|
|
969
|
+
headers=request_header,
|
|
970
|
+
timeout=REQUEST_TIMEOUT,
|
|
971
|
+
failure_message="Failed to get members of Core Share group -> '{}'".format(
|
|
972
|
+
group_id
|
|
973
|
+
),
|
|
974
|
+
user_credentials=False,
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
# end method definition
|
|
978
|
+
|
|
979
|
+
def add_group_member(
|
|
980
|
+
self, group_id: str, user_id: str, is_group_admin: bool = False
|
|
981
|
+
) -> list | None:
|
|
982
|
+
"""Add a Core Share user to a Core Share group.
|
|
983
|
+
|
|
984
|
+
Args:
|
|
985
|
+
group_id (str): ID of the Core Share Group
|
|
986
|
+
user_id (str): ID of the Core Share User
|
|
987
|
+
|
|
988
|
+
Returns:
|
|
989
|
+
list | None: Dictionary with the Core Share group membership or None if the request fails.
|
|
990
|
+
|
|
991
|
+
Example Response ('errors' is only output if success = False):
|
|
992
|
+
[
|
|
993
|
+
{
|
|
994
|
+
'member': 'alewis@qa.idea-te.eimdemo.com',
|
|
995
|
+
'success': True,
|
|
996
|
+
'user': {
|
|
997
|
+
'id': '2595801699801110696',
|
|
998
|
+
'email': 'alewis@qa.idea-te.eimdemo.com',
|
|
999
|
+
'otSaaSUID': '41325224-bbcf-4238-82b4-a9283be74821',
|
|
1000
|
+
'otSaaSPID': 'aa49f566-0874-41e9-9924-452852ebaf7a',
|
|
1001
|
+
'uri': '/api/v1/users/2595801699801110696',
|
|
1002
|
+
'tenantId': '2157293035593927996',
|
|
1003
|
+
'title': 'Records Manager',
|
|
1004
|
+
'company': 'Innovate',
|
|
1005
|
+
'lastName': 'Lewis',
|
|
1006
|
+
'firstName': 'Anne',
|
|
1007
|
+
'displayName': 'Lewis Anne',
|
|
1008
|
+
'type': 'user',
|
|
1009
|
+
'imageUri': 'https://core.opentext.com/api/v1/users/2595801699801110696/photo?id=0fbedc509fdfa1d27bcb5b3615714988e5f8e24598f0fc74b776ff049faef1f2',
|
|
1010
|
+
'thumbnailUri': 'https://core.opentext.com/api/v1/users/2595801699801110696/photo?s=small&id=0fbedc509fdfa1d27bcb5b3615714988e5f8e24598f0fc74b776ff049faef1f2',
|
|
1011
|
+
'defaultImageUri': False,
|
|
1012
|
+
'isConfirmed': True,
|
|
1013
|
+
'isEnabled': True
|
|
1014
|
+
}
|
|
1015
|
+
'errors': [
|
|
1016
|
+
{
|
|
1017
|
+
'code': 'groupInvitationExists',
|
|
1018
|
+
'message': 'The user has already been invited to the group'
|
|
1019
|
+
}
|
|
1020
|
+
]
|
|
1021
|
+
}
|
|
1022
|
+
]
|
|
1023
|
+
"""
|
|
1024
|
+
|
|
1025
|
+
if not self._access_token_admin:
|
|
1026
|
+
self.authenticate_admin()
|
|
1027
|
+
|
|
1028
|
+
request_header = self.request_header_admin()
|
|
1029
|
+
request_url = self.config()["groupsUrl"] + "/{}".format(group_id) + "/members"
|
|
1030
|
+
|
|
1031
|
+
user = self.get_user_by_id(user_id=user_id)
|
|
1032
|
+
user_email = self.get_result_value(response=user, key="email")
|
|
1033
|
+
|
|
1034
|
+
payload = {"members": [user_email], "specificGroupRole": is_group_admin}
|
|
1035
|
+
|
|
1036
|
+
logger.debug(
|
|
1037
|
+
"Add Core Share user -> '%s' (%s) as %s to Core Share group with ID -> %s; calling -> %s",
|
|
1038
|
+
user_email,
|
|
1039
|
+
user_id,
|
|
1040
|
+
"group member" if not is_group_admin else "group admin",
|
|
1041
|
+
group_id,
|
|
1042
|
+
request_url,
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
return self.do_request(
|
|
1046
|
+
url=request_url,
|
|
1047
|
+
method="POST",
|
|
1048
|
+
headers=request_header,
|
|
1049
|
+
json_data=payload,
|
|
1050
|
+
timeout=REQUEST_TIMEOUT,
|
|
1051
|
+
failure_message="Failed to add Core Share user -> '{}' to Core Share group with ID -> {}".format(
|
|
1052
|
+
user_email, group_id
|
|
1053
|
+
),
|
|
1054
|
+
user_credentials=False,
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
# end method definition
|
|
1058
|
+
|
|
1059
|
+
def remove_group_member(
|
|
1060
|
+
self, group_id: str, user_id: str, is_group_admin: bool = False
|
|
1061
|
+
) -> list | None:
|
|
1062
|
+
"""Remove a Core Share user from a Core Share group.
|
|
1063
|
+
|
|
1064
|
+
Args:
|
|
1065
|
+
group_id (str): ID of the Core Share Group
|
|
1066
|
+
user_id (str): ID of the Core Share User
|
|
1067
|
+
|
|
1068
|
+
Returns:
|
|
1069
|
+
list | None: Dictionary with the Core Share group membership or None if the request fails.
|
|
1070
|
+
|
|
1071
|
+
Example Response ('errors' is only output if success = False):
|
|
1072
|
+
[
|
|
1073
|
+
{
|
|
1074
|
+
'member': 'alewis@qa.idea-te.eimdemo.com',
|
|
1075
|
+
'success': True,
|
|
1076
|
+
'errors': [
|
|
1077
|
+
{
|
|
1078
|
+
'code': 'groupInvitationExists',
|
|
1079
|
+
'message': 'The user has already been invited to the group'
|
|
1080
|
+
}
|
|
1081
|
+
]
|
|
1082
|
+
}
|
|
1083
|
+
]
|
|
1084
|
+
"""
|
|
1085
|
+
|
|
1086
|
+
if not self._access_token_admin:
|
|
1087
|
+
self.authenticate_admin()
|
|
1088
|
+
|
|
1089
|
+
request_header = self.request_header_admin()
|
|
1090
|
+
request_url = self.config()["groupsUrl"] + "/{}".format(group_id) + "/members"
|
|
1091
|
+
|
|
1092
|
+
user = self.get_user_by_id(user_id=user_id)
|
|
1093
|
+
user_email = self.get_result_value(response=user, key="email")
|
|
1094
|
+
|
|
1095
|
+
payload = {"members": [user_email], "specificGroupRole": is_group_admin}
|
|
1096
|
+
|
|
1097
|
+
logger.debug(
|
|
1098
|
+
"Remove Core Share user -> '%s' (%s) as %s from Core Share group with ID -> %s; calling -> %s",
|
|
1099
|
+
user_email,
|
|
1100
|
+
user_id,
|
|
1101
|
+
"group member" if not is_group_admin else "group admin",
|
|
1102
|
+
group_id,
|
|
1103
|
+
request_url,
|
|
1104
|
+
)
|
|
1105
|
+
|
|
1106
|
+
return self.do_request(
|
|
1107
|
+
url=request_url,
|
|
1108
|
+
method="DELETE",
|
|
1109
|
+
headers=request_header,
|
|
1110
|
+
json_data=payload,
|
|
1111
|
+
timeout=REQUEST_TIMEOUT,
|
|
1112
|
+
failure_message="Failed to remove Core Share user -> '{}' ({}) from Core Share group with ID -> {}".format(
|
|
1113
|
+
user_email, user_id, group_id
|
|
1114
|
+
),
|
|
1115
|
+
user_credentials=False,
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
# end method definition
|
|
1119
|
+
|
|
1120
|
+
def get_group_by_id(self, group_id: str) -> dict | None:
|
|
1121
|
+
"""Get a Core Share group by its ID.
|
|
1122
|
+
|
|
1123
|
+
Args:
|
|
1124
|
+
None
|
|
1125
|
+
|
|
1126
|
+
Returns:
|
|
1127
|
+
dict | None: Dictionary with the Core Share group data or None if the request fails.
|
|
1128
|
+
|
|
1129
|
+
Response example:
|
|
1130
|
+
"""
|
|
1131
|
+
|
|
1132
|
+
if not self._access_token_admin:
|
|
1133
|
+
self.authenticate_admin()
|
|
1134
|
+
|
|
1135
|
+
request_header = self.request_header_admin()
|
|
1136
|
+
request_url = self.config()["groupsUrl"] + "/" + group_id
|
|
1137
|
+
|
|
1138
|
+
logger.debug(
|
|
1139
|
+
"Get Core Share group with ID -> %s; calling -> %s", group_id, request_url
|
|
1140
|
+
)
|
|
1141
|
+
|
|
1142
|
+
return self.do_request(
|
|
1143
|
+
url=request_url,
|
|
1144
|
+
method="GET",
|
|
1145
|
+
headers=request_header,
|
|
1146
|
+
timeout=REQUEST_TIMEOUT,
|
|
1147
|
+
failure_message="Failed to get Core Share group with ID -> {}".format(
|
|
1148
|
+
group_id
|
|
1149
|
+
),
|
|
1150
|
+
user_credentials=False,
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
# end method definition
|
|
1154
|
+
|
|
1155
|
+
def get_group_by_name(self, name: str) -> dict | None:
|
|
1156
|
+
"""Get Core Share group by its name.
|
|
1157
|
+
|
|
1158
|
+
Args:
|
|
1159
|
+
name (str): Name of the group to search.
|
|
1160
|
+
|
|
1161
|
+
Returns:
|
|
1162
|
+
dict | None: Dictionary with the Core Share group data or None if the request fails.
|
|
1163
|
+
|
|
1164
|
+
Example result:
|
|
1165
|
+
{
|
|
1166
|
+
'results': [
|
|
1167
|
+
{
|
|
1168
|
+
'id': '2594934169968578199',
|
|
1169
|
+
'type': 'group',
|
|
1170
|
+
'tenantId': '2157293035593927996',
|
|
1171
|
+
'displayName': 'Test Group',
|
|
1172
|
+
'name': 'Test Group',
|
|
1173
|
+
'createdAt': '2024-05-03T07:50:58.830Z',
|
|
1174
|
+
'uri': '/api/v1/groups/2594934169968578199',
|
|
1175
|
+
'imageuri': '/img/app/group-default-lrg.png',
|
|
1176
|
+
'thumbnailUri': '/img/app/group-default-sm.png',
|
|
1177
|
+
'defaultImageUri': True,
|
|
1178
|
+
'description': '',
|
|
1179
|
+
'tenantName': 'terrarium'
|
|
1180
|
+
}
|
|
1181
|
+
],
|
|
1182
|
+
'total': 1
|
|
1183
|
+
}
|
|
1184
|
+
"""
|
|
1185
|
+
|
|
1186
|
+
groups = self.search_groups(
|
|
1187
|
+
query_string=name,
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
return groups
|
|
1191
|
+
|
|
1192
|
+
# end method definition
|
|
1193
|
+
|
|
1194
|
+
def search_groups(self, query_string: str) -> dict | None:
|
|
1195
|
+
"""Search Core Share group(s) by name.
|
|
1196
|
+
|
|
1197
|
+
Args:
|
|
1198
|
+
query_string(str): Query for the group name / property
|
|
1199
|
+
|
|
1200
|
+
Returns:
|
|
1201
|
+
dict | None: Dictionary with the Core Share user data or None if the request fails.
|
|
1202
|
+
|
|
1203
|
+
Example response:
|
|
1204
|
+
"""
|
|
1205
|
+
|
|
1206
|
+
if not self._access_token_admin:
|
|
1207
|
+
self.authenticate_admin()
|
|
1208
|
+
|
|
1209
|
+
request_header = self.request_header_admin()
|
|
1210
|
+
request_url = self.config()["searchGroupUrl"] + "?q=" + query_string
|
|
1211
|
+
|
|
1212
|
+
logger.debug(
|
|
1213
|
+
"Search Core Share group by -> %s; calling -> %s", query_string, request_url
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1216
|
+
return self.do_request(
|
|
1217
|
+
url=request_url,
|
|
1218
|
+
method="GET",
|
|
1219
|
+
headers=request_header,
|
|
1220
|
+
timeout=REQUEST_TIMEOUT,
|
|
1221
|
+
failure_message="Cannot find Core Share group with name / property -> {}".format(
|
|
1222
|
+
query_string
|
|
1223
|
+
),
|
|
1224
|
+
user_credentials=False,
|
|
1225
|
+
)
|
|
1226
|
+
|
|
1227
|
+
# end method definition
|
|
1228
|
+
|
|
1229
|
+
def get_users(self) -> dict | None:
|
|
1230
|
+
"""Get Core Share users.
|
|
1231
|
+
|
|
1232
|
+
Args:
|
|
1233
|
+
None
|
|
1234
|
+
|
|
1235
|
+
Returns:
|
|
1236
|
+
dict | None: Dictionary with the Core Share user data or None if the request fails.
|
|
1237
|
+
|
|
1238
|
+
Example response (it is a list!):
|
|
1239
|
+
[
|
|
1240
|
+
{
|
|
1241
|
+
'id': '2400020228198108758',
|
|
1242
|
+
'type': 'user',
|
|
1243
|
+
'tenantId': '2157293035593927996',
|
|
1244
|
+
'firstName': 'Technical Marketing',
|
|
1245
|
+
'lastName': 'Service',
|
|
1246
|
+
'displayName': 'Technical Marketing Service',
|
|
1247
|
+
'title': 'Service User',
|
|
1248
|
+
'company': 'terrarium',
|
|
1249
|
+
'email': 'tm-service@opentext.com',
|
|
1250
|
+
'otSaaSUID': 'fdb07113-4854-4f63-a208-55759ee925ce',
|
|
1251
|
+
'otSaaSPID': 'aa49f566-0874-41e9-9924-452852ebaf7a',
|
|
1252
|
+
'state': 'enabled',
|
|
1253
|
+
'isEnabled': True,
|
|
1254
|
+
'isConfirmed': True,
|
|
1255
|
+
'quota': 2147483648,
|
|
1256
|
+
'usage': 10400,
|
|
1257
|
+
'uri': '/api/v1/users/2400020228198108758',
|
|
1258
|
+
'imageuri': '/img/app/profile-default-lrg.png',
|
|
1259
|
+
'thumbnailUri': '/img/app/topbar-profile-default-sm.png',
|
|
1260
|
+
'defaultImageUri': True
|
|
1261
|
+
'rootId': '2400020231108955735'
|
|
1262
|
+
'userRoot' : {
|
|
1263
|
+
{
|
|
1264
|
+
'size': 0,
|
|
1265
|
+
'id': '2400020231108955735',
|
|
1266
|
+
'resourceType': 1,
|
|
1267
|
+
'name': 'Files',
|
|
1268
|
+
'createdById': '2400020228198108758',
|
|
1269
|
+
'created': '2023-08-08T09:31:46.654Z',
|
|
1270
|
+
'lastModified': '2023-09-19T15:11:56.925Z',
|
|
1271
|
+
'lastModifiedById': '2400020228198108758',
|
|
1272
|
+
'currentVersionNumber': None,
|
|
1273
|
+
'currentVersionId': None,
|
|
1274
|
+
'childCount': '4',
|
|
1275
|
+
'shareCount': 1,
|
|
1276
|
+
'deleteCount': 0,
|
|
1277
|
+
'trashState': 0,
|
|
1278
|
+
'imageId': None,
|
|
1279
|
+
'thumbnailId': None,
|
|
1280
|
+
'tedsImageId': None,
|
|
1281
|
+
'tedsThumbnailId': None,
|
|
1282
|
+
'parentId': None,
|
|
1283
|
+
'tagCount': 0,
|
|
1284
|
+
'versionCommentCount': 0,
|
|
1285
|
+
'draftCommentCount': 0,
|
|
1286
|
+
'subTypeId': None,
|
|
1287
|
+
'contentOriginId': None,
|
|
1288
|
+
'externalData': None,
|
|
1289
|
+
'tenantId': '2157293035593927996',
|
|
1290
|
+
'nodeType': 1,
|
|
1291
|
+
'likesCount': 0,
|
|
1292
|
+
'commentCount': 0,
|
|
1293
|
+
'createdAt': '2023-08-08T09:31:46.655Z',
|
|
1294
|
+
'updatedAt': '2023-09-19T15:11:56.925Z'
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
...
|
|
1298
|
+
},
|
|
1299
|
+
...
|
|
1300
|
+
]
|
|
1301
|
+
"""
|
|
1302
|
+
|
|
1303
|
+
if not self._access_token_admin:
|
|
1304
|
+
self.authenticate_admin()
|
|
1305
|
+
|
|
1306
|
+
request_header = self.request_header_admin()
|
|
1307
|
+
request_url = self.config()["usersUrlv1"]
|
|
1308
|
+
|
|
1309
|
+
logger.debug("Get Core Share users; calling -> %s", request_url)
|
|
1310
|
+
|
|
1311
|
+
return self.do_request(
|
|
1312
|
+
url=request_url,
|
|
1313
|
+
method="GET",
|
|
1314
|
+
headers=request_header,
|
|
1315
|
+
timeout=REQUEST_TIMEOUT,
|
|
1316
|
+
failure_message="Failed to get Core Share users",
|
|
1317
|
+
user_credentials=False,
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
# end method definition
|
|
1321
|
+
|
|
1322
|
+
def get_user_by_id(self, user_id: str) -> dict | None:
|
|
1323
|
+
"""Get a Core Share user by its ID.
|
|
1324
|
+
|
|
1325
|
+
Args:
|
|
1326
|
+
None
|
|
1327
|
+
|
|
1328
|
+
Returns:
|
|
1329
|
+
dict | None: Dictionary with the Core Share user data or None if the request fails.
|
|
1330
|
+
|
|
1331
|
+
Response example:
|
|
1332
|
+
{
|
|
1333
|
+
'accessRoles': [],
|
|
1334
|
+
'commentCount': 0,
|
|
1335
|
+
'company': 'terrarium',
|
|
1336
|
+
'createdAt': '2024-04-19T11:58:34.240Z',
|
|
1337
|
+
'defaultImageUri': True,
|
|
1338
|
+
'disabledAt': None,
|
|
1339
|
+
'displayName': 'Sato Ken',
|
|
1340
|
+
'email': 'ksato@idea-te.eimdemo.com',
|
|
1341
|
+
'firstName': 'Ken',
|
|
1342
|
+
'id': '2584911925942946703',
|
|
1343
|
+
'otSaaSUID': '6cab5035-abbc-481c-b049-10b4efae7408',
|
|
1344
|
+
'otSaaSPID': 'aa49f566-0874-41e9-9924-452852ebaf7a',
|
|
1345
|
+
'imageUri': 'https://core.opentext.com/img/app/profile-default-lrg.png',
|
|
1346
|
+
'invitedAt': '2024-04-19T11:58:36.307Z',
|
|
1347
|
+
'isAdmin': False,
|
|
1348
|
+
'isConfirmed': True,
|
|
1349
|
+
'isEnabled': True,
|
|
1350
|
+
'isSync': False,
|
|
1351
|
+
'lastLoginDate': -1,
|
|
1352
|
+
'lastName': 'Sato',
|
|
1353
|
+
'likesCount': 0,
|
|
1354
|
+
'rootId': '2584911935422073756',
|
|
1355
|
+
'state': 'enabled',
|
|
1356
|
+
'stateChanged': '2024-04-19T12:03:23.736Z',
|
|
1357
|
+
'tenantId': '2157293035593927996',
|
|
1358
|
+
'thumbnailUri': 'https://core.opentext.com/img/app/topbar-profile-default-sm.png',
|
|
1359
|
+
'title': 'Real Estate Manager',
|
|
1360
|
+
'type': 'user',
|
|
1361
|
+
'updatedAt': '2024-04-19T12:03:23.731Z',
|
|
1362
|
+
'uri': '/api/v1/users/2584911925942946703',
|
|
1363
|
+
'userRoot': {
|
|
1364
|
+
'size': 0,
|
|
1365
|
+
'id': '2584911935422073756',
|
|
1366
|
+
'resourceType': 1,
|
|
1367
|
+
'name': 'Files',
|
|
1368
|
+
'createdById': '2584911925942946703',
|
|
1369
|
+
'created': '2024-04-19T11:58:35.370Z',
|
|
1370
|
+
'lastModified': '2024-04-19T11:58:35.370Z',
|
|
1371
|
+
'lastModifiedById': '2584911925942946703',
|
|
1372
|
+
'currentVersionNumber': None,
|
|
1373
|
+
'currentVersionId': None,
|
|
1374
|
+
'childCount': '0',
|
|
1375
|
+
'shareCount': 1,
|
|
1376
|
+
'deleteCount': 0,
|
|
1377
|
+
'trashState': 0,
|
|
1378
|
+
'imageId': None,
|
|
1379
|
+
'thumbnailId': None,
|
|
1380
|
+
'tedsImageId': None,
|
|
1381
|
+
'tedsThumbnailId': None,
|
|
1382
|
+
'parentId': None,
|
|
1383
|
+
...
|
|
1384
|
+
},
|
|
1385
|
+
'hasRequestedDelete': False,
|
|
1386
|
+
'defaultBaseUrl': 'https://core.opentext.com',
|
|
1387
|
+
'quota': 10737418240,
|
|
1388
|
+
'usage': 0
|
|
1389
|
+
}
|
|
1390
|
+
"""
|
|
1391
|
+
|
|
1392
|
+
if not self._access_token_user:
|
|
1393
|
+
self.authenticate_user()
|
|
1394
|
+
|
|
1395
|
+
request_header = self.request_header_user()
|
|
1396
|
+
request_url = self.config()["usersUrlv1"] + "/" + user_id
|
|
1397
|
+
|
|
1398
|
+
logger.debug(
|
|
1399
|
+
"Get Core Share user with ID -> %s; calling -> %s", user_id, request_url
|
|
1400
|
+
)
|
|
1401
|
+
|
|
1402
|
+
return self.do_request(
|
|
1403
|
+
url=request_url,
|
|
1404
|
+
method="GET",
|
|
1405
|
+
headers=request_header,
|
|
1406
|
+
timeout=REQUEST_TIMEOUT,
|
|
1407
|
+
failure_message="Failed to get Core Share user with ID -> {}".format(
|
|
1408
|
+
user_id
|
|
1409
|
+
),
|
|
1410
|
+
user_credentials=True,
|
|
1411
|
+
)
|
|
1412
|
+
|
|
1413
|
+
# end method definition
|
|
1414
|
+
|
|
1415
|
+
def get_user_by_name(
|
|
1416
|
+
self, first_name: str, last_name: str, user_status: str = "internal-native"
|
|
1417
|
+
) -> dict | None:
|
|
1418
|
+
"""Get Core Share user by its first and last name.
|
|
1419
|
+
|
|
1420
|
+
Args:
|
|
1421
|
+
first_name (str): First name of the users to search.
|
|
1422
|
+
last_name (str): Last name of the users to search.
|
|
1423
|
+
user_status (str, optional): type of users. Possible values:
|
|
1424
|
+
* internal-enabled
|
|
1425
|
+
* internal-pending
|
|
1426
|
+
* internal-locked
|
|
1427
|
+
* internal-native (non-SSO)
|
|
1428
|
+
* internal-sso
|
|
1429
|
+
|
|
1430
|
+
Returns:
|
|
1431
|
+
dict | None: Dictionary with the Core Share user data or None if the request fails.
|
|
1432
|
+
"""
|
|
1433
|
+
|
|
1434
|
+
# Search the users with this first and last name (and hope this is unique ;-).
|
|
1435
|
+
users = self.search_users(
|
|
1436
|
+
query_string=first_name + " " + last_name,
|
|
1437
|
+
user_status=user_status,
|
|
1438
|
+
)
|
|
1439
|
+
|
|
1440
|
+
return users
|
|
1441
|
+
|
|
1442
|
+
# end method definition
|
|
1443
|
+
|
|
1444
|
+
def get_user_by_email(
|
|
1445
|
+
self, email: str, user_status: str = "internal-native"
|
|
1446
|
+
) -> dict | None:
|
|
1447
|
+
"""Get Core Share user by its email address.
|
|
1448
|
+
|
|
1449
|
+
Args:
|
|
1450
|
+
email (str): Email address of the users to search.
|
|
1451
|
+
user_status (str, optional): type of users. Possible values:
|
|
1452
|
+
* internal-enabled
|
|
1453
|
+
* internal-pending
|
|
1454
|
+
* internal-locked
|
|
1455
|
+
* internal-native (non-SSO)
|
|
1456
|
+
* internal-sso
|
|
1457
|
+
|
|
1458
|
+
Returns:
|
|
1459
|
+
dict | None: Dictionary with the Core Share user data or None if the request fails.
|
|
1460
|
+
"""
|
|
1461
|
+
|
|
1462
|
+
# Search the users with this first and last name (and hope this is unique ;-).
|
|
1463
|
+
users = self.search_users(
|
|
1464
|
+
query_string=email,
|
|
1465
|
+
user_status=user_status,
|
|
1466
|
+
)
|
|
1467
|
+
|
|
1468
|
+
return users
|
|
1469
|
+
|
|
1470
|
+
# end method definition
|
|
1471
|
+
|
|
1472
|
+
def search_users(
|
|
1473
|
+
self,
|
|
1474
|
+
query_string: str,
|
|
1475
|
+
user_status: str = "internal-native",
|
|
1476
|
+
page_size: int = 100,
|
|
1477
|
+
) -> dict | None:
|
|
1478
|
+
"""Search Core Share user(s) by name / property. Needs to be a Tenant Administrator to do so.
|
|
1479
|
+
|
|
1480
|
+
Args:
|
|
1481
|
+
query_string (str): string to query the user(s)
|
|
1482
|
+
user_status (str, optional): type of users. Possible values:
|
|
1483
|
+
* internal-enabled
|
|
1484
|
+
* internal-pending
|
|
1485
|
+
* internal-locked
|
|
1486
|
+
* internal-native (non-SSO)
|
|
1487
|
+
* internal-sso
|
|
1488
|
+
page_size (int, optional): max number of results per page. We set the default to 100 (Web UI uses 25)
|
|
1489
|
+
|
|
1490
|
+
Returns:
|
|
1491
|
+
dict | None: Dictionary with the Core Share user data or None if the request fails.
|
|
1492
|
+
|
|
1493
|
+
Example response:
|
|
1494
|
+
{
|
|
1495
|
+
"results": [
|
|
1496
|
+
{
|
|
1497
|
+
"id": "2422698421996494632",
|
|
1498
|
+
"type": "user",
|
|
1499
|
+
"tenantId": "2157293035593927996",
|
|
1500
|
+
"firstName": "Andy",
|
|
1501
|
+
"lastName": "Wyatt",
|
|
1502
|
+
"displayName": "Andy Wyatt",
|
|
1503
|
+
"title": "Buyer",
|
|
1504
|
+
"company": "terrarium",
|
|
1505
|
+
"email": "awyatt@M365x46777101.onmicrosoft.com",
|
|
1506
|
+
"otSaaSUID": "0842d1e1-acfc-425b-994a-e2dcb4d333c6",
|
|
1507
|
+
"otSaaSPID": "aa49f566-0874-41e9-9924-452852ebaf7a",
|
|
1508
|
+
"state": "enabled",
|
|
1509
|
+
"isEnabled": true,
|
|
1510
|
+
"isConfirmed": true,
|
|
1511
|
+
"isAdmin": false,
|
|
1512
|
+
"accessRoles": [],
|
|
1513
|
+
"hasBeenDelegated": null,
|
|
1514
|
+
"createdAt": "2023-09-08T16:29:17.680Z",
|
|
1515
|
+
"lastLoginDate": "2023-10-05T16:14:16Z",
|
|
1516
|
+
"quota": 1073741824,
|
|
1517
|
+
"usage": 0,
|
|
1518
|
+
"rootId": "2422698425964306217",
|
|
1519
|
+
"uri": "/api/v1/users/2422698421996494632",
|
|
1520
|
+
"imageuri": "/img/app/profile-default-lrg.png",
|
|
1521
|
+
"thumbnailUri": "/img/app/topbar-profile-default-sm.png",
|
|
1522
|
+
"defaultImageUri": true
|
|
1523
|
+
},
|
|
1524
|
+
...
|
|
1525
|
+
]
|
|
1526
|
+
}
|
|
1527
|
+
"""
|
|
1528
|
+
|
|
1529
|
+
if not self._access_token_admin:
|
|
1530
|
+
self.authenticate_admin()
|
|
1531
|
+
|
|
1532
|
+
request_header = self.request_header_admin()
|
|
1533
|
+
request_url = (
|
|
1534
|
+
self.config()["searchUserUrl"]
|
|
1535
|
+
+ "/{}".format(user_status)
|
|
1536
|
+
+ "?q="
|
|
1537
|
+
+ query_string
|
|
1538
|
+
+ "&pageSize="
|
|
1539
|
+
+ str(page_size)
|
|
1540
|
+
)
|
|
1541
|
+
|
|
1542
|
+
logger.debug(
|
|
1543
|
+
"Search Core Share user by -> %s; calling -> %s", query_string, request_url
|
|
1544
|
+
)
|
|
1545
|
+
|
|
1546
|
+
return self.do_request(
|
|
1547
|
+
url=request_url,
|
|
1548
|
+
method="GET",
|
|
1549
|
+
headers=request_header,
|
|
1550
|
+
timeout=REQUEST_TIMEOUT,
|
|
1551
|
+
failure_message="Failed to search Core Share user with name / property -> {}".format(
|
|
1552
|
+
query_string
|
|
1553
|
+
),
|
|
1554
|
+
user_credentials=False,
|
|
1555
|
+
)
|
|
1556
|
+
|
|
1557
|
+
# end method definition
|
|
1558
|
+
|
|
1559
|
+
def add_user(
|
|
1560
|
+
self,
|
|
1561
|
+
first_name: str,
|
|
1562
|
+
last_name: str,
|
|
1563
|
+
email: str,
|
|
1564
|
+
password: str | None = None,
|
|
1565
|
+
title: str | None = None,
|
|
1566
|
+
company: str | None = None,
|
|
1567
|
+
) -> dict | None:
|
|
1568
|
+
"""Add a new Core Share user. This requires a Tenent Admin authorization.
|
|
1569
|
+
|
|
1570
|
+
Args:
|
|
1571
|
+
first_name (str): First name of the new user
|
|
1572
|
+
last_name (str): Last name of the new user
|
|
1573
|
+
email (str): Email of the new Core Share user
|
|
1574
|
+
password (str | None, optional): Password of the new Core Share user
|
|
1575
|
+
title (str | None, optional): Title of the user
|
|
1576
|
+
company (str | None, optional): Name of the Company of the user
|
|
1577
|
+
|
|
1578
|
+
Returns:
|
|
1579
|
+
dict | None: Dictionary with the Core Share User data or None if the request fails.
|
|
1580
|
+
|
|
1581
|
+
Example response:
|
|
1582
|
+
{
|
|
1583
|
+
"accessRoles": [],
|
|
1584
|
+
"commentCount": 0,
|
|
1585
|
+
"company": "terrarium",
|
|
1586
|
+
"createdAt": "2024-05-01T09:43:22.962Z",
|
|
1587
|
+
"defaultImageUri": true,
|
|
1588
|
+
"disabledAt": null,
|
|
1589
|
+
"displayName": "Tester Theo",
|
|
1590
|
+
"email": "theo@tester.com",
|
|
1591
|
+
"firstName": "Theo",
|
|
1592
|
+
"id": "2593541192377435562",
|
|
1593
|
+
"otSaaSUID": "77043e17-105c-418f-b4ba-1bef9f15937c",
|
|
1594
|
+
"otSaaSPID": "aa49f566-0874-41e9-9924-452852ebaf7a",
|
|
1595
|
+
"imageUri": "https://core.opentext.com/img/app/profile-default-lrg.png",
|
|
1596
|
+
"invitedAt": "2024-05-01T09:43:23.658Z",
|
|
1597
|
+
"isAdmin": false,
|
|
1598
|
+
"isConfirmed": false,
|
|
1599
|
+
"isEnabled": true,
|
|
1600
|
+
"isSync": false,
|
|
1601
|
+
"lastLoginDate": -1,
|
|
1602
|
+
"lastName": "Tester",
|
|
1603
|
+
"likesCount": 0,
|
|
1604
|
+
"rootId": "2593541195170842028",
|
|
1605
|
+
"state": "pending",
|
|
1606
|
+
"stateChanged": "2024-05-01T09:43:22.959Z",
|
|
1607
|
+
"tenantId": "2157293035593927996",
|
|
1608
|
+
"thumbnailUri": "https://core.opentext.com/img/app/topbar-profile-default-sm.png",
|
|
1609
|
+
"title": "VP Product Management",
|
|
1610
|
+
"type": "user",
|
|
1611
|
+
"updatedAt": "2024-05-01T09:43:23.658Z",
|
|
1612
|
+
"uri": "/api/v1/users/2593541192377435562",
|
|
1613
|
+
"hasRequestedDelete": false,
|
|
1614
|
+
"defaultBaseUrl": "https://core.opentext.com",
|
|
1615
|
+
"quota": 10737418240,
|
|
1616
|
+
"usage": 0
|
|
1617
|
+
}
|
|
1618
|
+
"""
|
|
1619
|
+
|
|
1620
|
+
if not self._access_token_admin:
|
|
1621
|
+
self.authenticate_admin()
|
|
1622
|
+
|
|
1623
|
+
# here we want the request to determine the content type automatically:
|
|
1624
|
+
request_header = self.request_header_admin(content_type="")
|
|
1625
|
+
request_url = self.config()["invitesUrl"]
|
|
1626
|
+
|
|
1627
|
+
payload = {
|
|
1628
|
+
"firstName": first_name,
|
|
1629
|
+
"lastName": last_name,
|
|
1630
|
+
"email": email,
|
|
1631
|
+
"quota": 10737418240,
|
|
1632
|
+
}
|
|
1633
|
+
if password:
|
|
1634
|
+
payload["password"] = password
|
|
1635
|
+
if title:
|
|
1636
|
+
payload["title"] = title
|
|
1637
|
+
if company:
|
|
1638
|
+
payload["company"] = company
|
|
1639
|
+
|
|
1640
|
+
logger.debug(
|
|
1641
|
+
"Adding Core Share user -> %s %s; calling -> %s",
|
|
1642
|
+
first_name,
|
|
1643
|
+
last_name,
|
|
1644
|
+
request_url,
|
|
1645
|
+
)
|
|
1646
|
+
|
|
1647
|
+
return self.do_request(
|
|
1648
|
+
url=request_url,
|
|
1649
|
+
method="POST",
|
|
1650
|
+
headers=request_header,
|
|
1651
|
+
json_data=payload,
|
|
1652
|
+
timeout=REQUEST_TIMEOUT,
|
|
1653
|
+
failure_message="Failed to add Core Share user -> '{} {}' ({})".format(
|
|
1654
|
+
first_name, last_name, email
|
|
1655
|
+
),
|
|
1656
|
+
user_credentials=False,
|
|
1657
|
+
)
|
|
1658
|
+
|
|
1659
|
+
# end method definition
|
|
1660
|
+
|
|
1661
|
+
def resend_user_invite(self, user_id: str) -> dict:
|
|
1662
|
+
"""Resend the invite for a Core Share user.
|
|
1663
|
+
|
|
1664
|
+
Args:
|
|
1665
|
+
user_id (str): The Core Share user ID.
|
|
1666
|
+
|
|
1667
|
+
Returns:
|
|
1668
|
+
dict: Response from the Core Share API.
|
|
1669
|
+
"""
|
|
1670
|
+
|
|
1671
|
+
if not self._access_token_admin:
|
|
1672
|
+
self.authenticate_admin()
|
|
1673
|
+
|
|
1674
|
+
request_header = self.request_header_admin()
|
|
1675
|
+
|
|
1676
|
+
request_url = self.config()["usersUrlv1"] + "/{}".format(user_id)
|
|
1677
|
+
|
|
1678
|
+
logger.debug(
|
|
1679
|
+
"Resend invite for Core Share user with ID -> %s; calling -> %s",
|
|
1680
|
+
user_id,
|
|
1681
|
+
request_url,
|
|
1682
|
+
)
|
|
1683
|
+
|
|
1684
|
+
update_data = {"resend": True}
|
|
1685
|
+
|
|
1686
|
+
return self.do_request(
|
|
1687
|
+
url=request_url,
|
|
1688
|
+
method="PUT",
|
|
1689
|
+
headers=request_header,
|
|
1690
|
+
json_data=update_data,
|
|
1691
|
+
timeout=REQUEST_TIMEOUT,
|
|
1692
|
+
failure_message="Failed to resend invite for Core Share user with ID -> {}".format(
|
|
1693
|
+
user_id
|
|
1694
|
+
),
|
|
1695
|
+
user_credentials=False,
|
|
1696
|
+
)
|
|
1697
|
+
|
|
1698
|
+
# end method definition
|
|
1699
|
+
|
|
1700
|
+
def update_user(self, user_id: str, update_data: dict) -> dict:
|
|
1701
|
+
"""Update a Core Share user.
|
|
1702
|
+
|
|
1703
|
+
Args:
|
|
1704
|
+
user_id (str): ID of the Core Share user.
|
|
1705
|
+
|
|
1706
|
+
Returns:
|
|
1707
|
+
dict: Response or None if the request has failed.
|
|
1708
|
+
"""
|
|
1709
|
+
|
|
1710
|
+
if not self._access_token_admin:
|
|
1711
|
+
self.authenticate_admin()
|
|
1712
|
+
|
|
1713
|
+
request_header = self.request_header_admin()
|
|
1714
|
+
|
|
1715
|
+
request_url = self.config()["usersUrlv1"] + "/{}".format(user_id)
|
|
1716
|
+
|
|
1717
|
+
logger.debug(
|
|
1718
|
+
"Update data of Core Share user with ID -> %s; calling -> %s",
|
|
1719
|
+
user_id,
|
|
1720
|
+
request_url,
|
|
1721
|
+
)
|
|
1722
|
+
|
|
1723
|
+
if "email" in update_data and not "password" in update_data:
|
|
1724
|
+
logger.warning(
|
|
1725
|
+
"Trying to update the email without providing the password. This is likely to fail..."
|
|
1726
|
+
)
|
|
1727
|
+
|
|
1728
|
+
return self.do_request(
|
|
1729
|
+
url=request_url,
|
|
1730
|
+
method="PUT",
|
|
1731
|
+
headers=request_header,
|
|
1732
|
+
json_data=update_data,
|
|
1733
|
+
timeout=REQUEST_TIMEOUT,
|
|
1734
|
+
failure_message="Failed to update Core Share user with ID -> {}".format(
|
|
1735
|
+
user_id
|
|
1736
|
+
),
|
|
1737
|
+
user_credentials=False,
|
|
1738
|
+
)
|
|
1739
|
+
|
|
1740
|
+
# end method definition
|
|
1741
|
+
|
|
1742
|
+
def add_user_access_role(self, user_id: str, role_id: int) -> dict:
|
|
1743
|
+
"""Add an access role to a Core Share user.
|
|
1744
|
+
|
|
1745
|
+
Args:
|
|
1746
|
+
user_id (str): The Core Share user ID.
|
|
1747
|
+
role_id (int): The role ID:
|
|
1748
|
+
* Content Manager = 5
|
|
1749
|
+
* Group Admin = 3
|
|
1750
|
+
|
|
1751
|
+
Returns:
|
|
1752
|
+
dict: Response from the Core Share API.
|
|
1753
|
+
"""
|
|
1754
|
+
|
|
1755
|
+
if not self._access_token_admin:
|
|
1756
|
+
self.authenticate_admin()
|
|
1757
|
+
|
|
1758
|
+
request_header = self.request_header_admin()
|
|
1759
|
+
|
|
1760
|
+
request_url = (
|
|
1761
|
+
self.config()["usersUrlv1"]
|
|
1762
|
+
+ "/{}".format(user_id)
|
|
1763
|
+
+ "/roles/"
|
|
1764
|
+
+ str(role_id)
|
|
1765
|
+
)
|
|
1766
|
+
|
|
1767
|
+
logger.debug(
|
|
1768
|
+
"Add access role -> %s to Core Share user with ID -> %s; calling -> %s",
|
|
1769
|
+
str(role_id),
|
|
1770
|
+
user_id,
|
|
1771
|
+
request_url,
|
|
1772
|
+
)
|
|
1773
|
+
|
|
1774
|
+
return self.do_request(
|
|
1775
|
+
url=request_url,
|
|
1776
|
+
method="PUT",
|
|
1777
|
+
headers=request_header,
|
|
1778
|
+
timeout=REQUEST_TIMEOUT,
|
|
1779
|
+
failure_message="Failed to add access role with ID -> {} to Core Share user with ID -> {}".format(
|
|
1780
|
+
role_id, user_id
|
|
1781
|
+
),
|
|
1782
|
+
user_credentials=False,
|
|
1783
|
+
)
|
|
1784
|
+
|
|
1785
|
+
# end method definition
|
|
1786
|
+
|
|
1787
|
+
def remove_user_access_role(self, user_id: str, role_id: int) -> dict:
|
|
1788
|
+
"""Remove an access role from a Core Share user.
|
|
1789
|
+
|
|
1790
|
+
Args:
|
|
1791
|
+
user_id (str): The Core Share user ID.
|
|
1792
|
+
role_id (int): The role ID:
|
|
1793
|
+
* Content Manager = 5
|
|
1794
|
+
* Group Admin = 3
|
|
1795
|
+
|
|
1796
|
+
Returns:
|
|
1797
|
+
dict: Response from the Core Share API.
|
|
1798
|
+
"""
|
|
1799
|
+
|
|
1800
|
+
if not self._access_token_admin:
|
|
1801
|
+
self.authenticate_admin()
|
|
1802
|
+
|
|
1803
|
+
request_header = self.request_header_admin()
|
|
1804
|
+
|
|
1805
|
+
request_url = (
|
|
1806
|
+
self.config()["usersUrlv1"]
|
|
1807
|
+
+ "/{}".format(user_id)
|
|
1808
|
+
+ "/roles/"
|
|
1809
|
+
+ str(role_id)
|
|
1810
|
+
)
|
|
1811
|
+
|
|
1812
|
+
logger.debug(
|
|
1813
|
+
"Remove access role with ID -> %s from Core Share user with ID -> %s; calling -> %s",
|
|
1814
|
+
str(role_id),
|
|
1815
|
+
user_id,
|
|
1816
|
+
request_url,
|
|
1817
|
+
)
|
|
1818
|
+
|
|
1819
|
+
return self.do_request(
|
|
1820
|
+
url=request_url,
|
|
1821
|
+
method="DELETE",
|
|
1822
|
+
headers=request_header,
|
|
1823
|
+
timeout=REQUEST_TIMEOUT,
|
|
1824
|
+
failure_message="Failed to remove access role with ID -> {} from Core Share user with ID -> {}".format(
|
|
1825
|
+
role_id, user_id
|
|
1826
|
+
),
|
|
1827
|
+
user_credentials=False,
|
|
1828
|
+
)
|
|
1829
|
+
|
|
1830
|
+
# end method definition
|
|
1831
|
+
|
|
1832
|
+
def update_user_access_roles(
|
|
1833
|
+
self,
|
|
1834
|
+
user_id: str,
|
|
1835
|
+
is_admin: bool | None = None,
|
|
1836
|
+
is_content_manager: bool | None = None,
|
|
1837
|
+
is_group_admin: bool | None = None,
|
|
1838
|
+
) -> dict:
|
|
1839
|
+
"""Define the access roles of a Core Share user.
|
|
1840
|
+
|
|
1841
|
+
Args:
|
|
1842
|
+
user_id (str): ID of the Core Share user
|
|
1843
|
+
is_content_manager (bool | None, optional): Assign Content Manager Role if True.
|
|
1844
|
+
Removes Content Manager Role if False.
|
|
1845
|
+
Does nothing if None.
|
|
1846
|
+
Defaults to None.
|
|
1847
|
+
is_group_admin (bool | None, optional): Assign Group Admin Role if True.
|
|
1848
|
+
Removes Group Admin Role if False.
|
|
1849
|
+
Does nothing if None.
|
|
1850
|
+
Defaults to None.
|
|
1851
|
+
is_admin (bool | None, optional): Makes user Admin if True.
|
|
1852
|
+
Removes Admin rights if False.
|
|
1853
|
+
Does nothing if None.
|
|
1854
|
+
Defaults to None.
|
|
1855
|
+
|
|
1856
|
+
Returns:
|
|
1857
|
+
dict: Response from the Core Share API.
|
|
1858
|
+
"""
|
|
1859
|
+
|
|
1860
|
+
CONTENT_MANAGER_ROLE_ID = 5
|
|
1861
|
+
GROUP_ADMIN_ROLE_ID = 3
|
|
1862
|
+
|
|
1863
|
+
response = None
|
|
1864
|
+
|
|
1865
|
+
# Admins don't have/need specific access roles. They are controled by isAdmin flag.
|
|
1866
|
+
if is_admin is not None:
|
|
1867
|
+
update_data = {}
|
|
1868
|
+
update_data["isAdmin"] = is_admin
|
|
1869
|
+
response = self.update_user(user_id=user_id, update_data=update_data)
|
|
1870
|
+
|
|
1871
|
+
# Only for non-admins the other two roles are usable:
|
|
1872
|
+
if is_content_manager is not None:
|
|
1873
|
+
if is_content_manager:
|
|
1874
|
+
response = self.add_user_access_role(
|
|
1875
|
+
user_id=user_id, role_id=CONTENT_MANAGER_ROLE_ID
|
|
1876
|
+
)
|
|
1877
|
+
else:
|
|
1878
|
+
response = self.remove_user_access_role(
|
|
1879
|
+
user_id=user_id, role_id=CONTENT_MANAGER_ROLE_ID
|
|
1880
|
+
)
|
|
1881
|
+
|
|
1882
|
+
if is_group_admin is not None:
|
|
1883
|
+
if is_group_admin:
|
|
1884
|
+
response = self.add_user_access_role(
|
|
1885
|
+
user_id=user_id, role_id=GROUP_ADMIN_ROLE_ID
|
|
1886
|
+
)
|
|
1887
|
+
else:
|
|
1888
|
+
response = self.remove_user_access_role(
|
|
1889
|
+
user_id=user_id, role_id=GROUP_ADMIN_ROLE_ID
|
|
1890
|
+
)
|
|
1891
|
+
|
|
1892
|
+
return response
|
|
1893
|
+
|
|
1894
|
+
# end method definition
|
|
1895
|
+
|
|
1896
|
+
def update_user_password(
|
|
1897
|
+
self, user_id: str, password: str, new_password: str
|
|
1898
|
+
) -> dict:
|
|
1899
|
+
"""Update the password of a Core Share user.
|
|
1900
|
+
|
|
1901
|
+
Args:
|
|
1902
|
+
user_id (str): The Core Share user ID.
|
|
1903
|
+
password (str): Old user password.
|
|
1904
|
+
new_password (str): New user password.
|
|
1905
|
+
|
|
1906
|
+
Returns:
|
|
1907
|
+
dict: Response from the Core Share API.
|
|
1908
|
+
"""
|
|
1909
|
+
|
|
1910
|
+
if not self._access_token_admin:
|
|
1911
|
+
self.authenticate_admin()
|
|
1912
|
+
|
|
1913
|
+
request_header = self.request_header_admin()
|
|
1914
|
+
|
|
1915
|
+
request_url = self.config()["usersUrlv1"] + "/{}".format(user_id)
|
|
1916
|
+
|
|
1917
|
+
logger.debug(
|
|
1918
|
+
"Update password of Core Share user with ID -> %s; calling -> %s",
|
|
1919
|
+
user_id,
|
|
1920
|
+
request_url,
|
|
1921
|
+
)
|
|
1922
|
+
|
|
1923
|
+
update_data = {"password": password, "newpassword": new_password}
|
|
1924
|
+
|
|
1925
|
+
return self.do_request(
|
|
1926
|
+
url=request_url,
|
|
1927
|
+
method="PUT",
|
|
1928
|
+
headers=request_header,
|
|
1929
|
+
json_data=update_data,
|
|
1930
|
+
timeout=REQUEST_TIMEOUT,
|
|
1931
|
+
failure_message="Failed to update password of Core Share user with ID -> {}".format(
|
|
1932
|
+
user_id
|
|
1933
|
+
),
|
|
1934
|
+
user_credentials=False,
|
|
1935
|
+
)
|
|
1936
|
+
|
|
1937
|
+
# end method definition
|
|
1938
|
+
|
|
1939
|
+
def update_user_photo(
|
|
1940
|
+
self, user_id: str, photo_path: str, mime_type: str = "image/jpeg"
|
|
1941
|
+
) -> dict | None:
|
|
1942
|
+
"""Update the Core Share user photo.
|
|
1943
|
+
|
|
1944
|
+
Args:
|
|
1945
|
+
user_id (str): Core Share ID of the user
|
|
1946
|
+
photo_path (str): file system path with the location of the photo
|
|
1947
|
+
Returns:
|
|
1948
|
+
dict | None: Dictionary with the Core Share User data or None if the request fails.
|
|
1949
|
+
"""
|
|
1950
|
+
|
|
1951
|
+
if not self._access_token_user:
|
|
1952
|
+
self.authenticate_user()
|
|
1953
|
+
|
|
1954
|
+
# Check if the photo file exists
|
|
1955
|
+
if not os.path.isfile(photo_path):
|
|
1956
|
+
logger.error("Photo file -> %s not found!", photo_path)
|
|
1957
|
+
return None
|
|
1958
|
+
|
|
1959
|
+
try:
|
|
1960
|
+
# Read the photo file as binary data
|
|
1961
|
+
with open(photo_path, "rb") as image_file:
|
|
1962
|
+
photo_data = image_file.read()
|
|
1963
|
+
except OSError as exception:
|
|
1964
|
+
# Handle any errors that occurred while reading the photo file
|
|
1965
|
+
logger.error(
|
|
1966
|
+
"Error reading photo file -> %s; error -> %s", photo_path, exception
|
|
1967
|
+
)
|
|
1968
|
+
return None
|
|
1969
|
+
|
|
1970
|
+
request_url = self.config()["usersUrlv3"] + "/{}".format(user_id) + "/photo"
|
|
1971
|
+
files = {
|
|
1972
|
+
"file": (photo_path, photo_data, mime_type),
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
logger.debug(
|
|
1976
|
+
"Update profile photo of Core Share user with ID -> %s; calling -> %s",
|
|
1977
|
+
user_id,
|
|
1978
|
+
request_url,
|
|
1979
|
+
)
|
|
1980
|
+
|
|
1981
|
+
return self.do_request(
|
|
1982
|
+
url=request_url,
|
|
1983
|
+
method="POST",
|
|
1984
|
+
headers=self.request_header_user(content_type=""),
|
|
1985
|
+
files=files,
|
|
1986
|
+
timeout=REQUEST_TIMEOUT,
|
|
1987
|
+
failure_message="Failed to update profile photo of Core Share user with ID -> {}".format(
|
|
1988
|
+
user_id
|
|
1989
|
+
),
|
|
1990
|
+
user_credentials=True,
|
|
1991
|
+
verify=False,
|
|
1992
|
+
)
|
|
1993
|
+
|
|
1994
|
+
# end method definition
|
|
1995
|
+
|
|
1996
|
+
def get_folders(self, parent_id: str) -> list | None:
|
|
1997
|
+
"""Get Core Share folders under a given parent ID. This runs under user credentials (not admin!)
|
|
1998
|
+
|
|
1999
|
+
Args:
|
|
2000
|
+
parent_id (str): ID of the parent folder or the rootID of a user
|
|
2001
|
+
|
|
2002
|
+
Returns:
|
|
2003
|
+
list | None: List with the Core Share folders data or None if the request fails.
|
|
2004
|
+
|
|
2005
|
+
Example response (it is a list!):
|
|
2006
|
+
[
|
|
2007
|
+
{
|
|
2008
|
+
'id': '2599466250228733940',
|
|
2009
|
+
'name': 'Global Trade AG (50031)',
|
|
2010
|
+
'size': 0,
|
|
2011
|
+
'created': '2024-05-09T13:55:24.899Z',
|
|
2012
|
+
'lastModified': '2024-05-09T13:55:33.069Z',
|
|
2013
|
+
'shareCount': 2,
|
|
2014
|
+
'isShared': True,
|
|
2015
|
+
'parentId': '2599466244163770353',
|
|
2016
|
+
'uri': '/api/v1/folders/2599466250228733940',
|
|
2017
|
+
'commentCount': 0,
|
|
2018
|
+
'isDeleted': False,
|
|
2019
|
+
'isLiked': False,
|
|
2020
|
+
'likesCount': 0,
|
|
2021
|
+
'locks': [],
|
|
2022
|
+
'createdBy': {
|
|
2023
|
+
'id': '2597156105373095597',
|
|
2024
|
+
'email': '6ccf1cb3-177e-4930-8baf-2d421cf92a5f',
|
|
2025
|
+
'uri': '/api/v1/users/2597156105373095597',
|
|
2026
|
+
'tenantId': '2595192600759637225',
|
|
2027
|
+
'tier': 'tier3',
|
|
2028
|
+
'title': '',
|
|
2029
|
+
'company': '',
|
|
2030
|
+
'lastName': '',
|
|
2031
|
+
'firstName': 'OpenText Service User',
|
|
2032
|
+
'displayName': 'OpenText Service User',
|
|
2033
|
+
'type': 'user',
|
|
2034
|
+
'imageUri': 'https://core.opentext.com/img/app/profile-default-lrg.png',
|
|
2035
|
+
'thumbnailUri': 'https://core.opentext.com/img/app/topbar-profile-default-sm.png',
|
|
2036
|
+
'defaultImageUri': True,
|
|
2037
|
+
'isConfirmed': True,
|
|
2038
|
+
'isEnabled': True
|
|
2039
|
+
},
|
|
2040
|
+
'lastModifiedBy': {...},
|
|
2041
|
+
'owner': {...},
|
|
2042
|
+
'permission': 1,
|
|
2043
|
+
'hasAttachments': False,
|
|
2044
|
+
'resourceType': 'folder',
|
|
2045
|
+
'tagCount': 0,
|
|
2046
|
+
'resourceSubType': {},
|
|
2047
|
+
'contentOriginId': '0D949C67-473D-448C-8F4B-B2CCA769F586',
|
|
2048
|
+
'externalData': None,
|
|
2049
|
+
'childCount': 7,
|
|
2050
|
+
'contentOriginator': {
|
|
2051
|
+
'id': '0D949C67-473D-448C-8F4B-B2CCA769F586',
|
|
2052
|
+
'name': 'IDEA-TE-QA',
|
|
2053
|
+
'imageUri': '/api/v1/tenants/2595192600759637225/contentOriginator/images/0D949C67-473D-448C-8F4B-B2CCA769F586'
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
]
|
|
2057
|
+
"""
|
|
2058
|
+
|
|
2059
|
+
if not self._access_token_user:
|
|
2060
|
+
self.authenticate_user()
|
|
2061
|
+
|
|
2062
|
+
request_header = self.request_header_user()
|
|
2063
|
+
request_url = (
|
|
2064
|
+
self.config()["foldersUrlv1"]
|
|
2065
|
+
+ "/{}".format(parent_id)
|
|
2066
|
+
+ "/children"
|
|
2067
|
+
+ "?limit=25&order=lastModified:desc&filter=any"
|
|
2068
|
+
)
|
|
2069
|
+
|
|
2070
|
+
logger.debug(
|
|
2071
|
+
"Get Core Share folders under parent -> %s; calling -> %s",
|
|
2072
|
+
parent_id,
|
|
2073
|
+
request_url,
|
|
2074
|
+
)
|
|
2075
|
+
|
|
2076
|
+
return self.do_request(
|
|
2077
|
+
url=request_url,
|
|
2078
|
+
method="GET",
|
|
2079
|
+
headers=request_header,
|
|
2080
|
+
timeout=REQUEST_TIMEOUT,
|
|
2081
|
+
failure_message="Failed to get Core Share folders under parent -> {}".format(
|
|
2082
|
+
parent_id
|
|
2083
|
+
),
|
|
2084
|
+
user_credentials=True,
|
|
2085
|
+
)
|
|
2086
|
+
|
|
2087
|
+
# end method definition
|
|
2088
|
+
|
|
2089
|
+
def unshare_folder(self, resource_id: str) -> dict | None:
|
|
2090
|
+
"""Unshare Core Share folder with a given resource ID.
|
|
2091
|
+
|
|
2092
|
+
Args:
|
|
2093
|
+
resource_id (str): ID of the folder (resource) to unshare with all collaborators
|
|
2094
|
+
|
|
2095
|
+
Returns:
|
|
2096
|
+
dict | None: Dictionary with the Core Share folders data or None if the request fails.
|
|
2097
|
+
|
|
2098
|
+
Example response (it is a list!):
|
|
2099
|
+
"""
|
|
2100
|
+
|
|
2101
|
+
if not self._access_token_user:
|
|
2102
|
+
self.authenticate_user()
|
|
2103
|
+
|
|
2104
|
+
request_header = self.request_header_user()
|
|
2105
|
+
request_url = (
|
|
2106
|
+
self.config()["foldersUrlv1"] + "/{}".format(resource_id) + "/collaborators"
|
|
2107
|
+
)
|
|
2108
|
+
|
|
2109
|
+
logger.debug(
|
|
2110
|
+
"Unshare Core Share folder -> %s; calling -> %s",
|
|
2111
|
+
resource_id,
|
|
2112
|
+
request_url,
|
|
2113
|
+
)
|
|
2114
|
+
|
|
2115
|
+
return self.do_request(
|
|
2116
|
+
url=request_url,
|
|
2117
|
+
method="DELETE",
|
|
2118
|
+
headers=request_header,
|
|
2119
|
+
timeout=REQUEST_TIMEOUT,
|
|
2120
|
+
failure_message="Failed to unshare Core Share folder with ID -> {}".format(
|
|
2121
|
+
resource_id
|
|
2122
|
+
),
|
|
2123
|
+
user_credentials=True,
|
|
2124
|
+
)
|
|
2125
|
+
|
|
2126
|
+
# end method definition
|
|
2127
|
+
|
|
2128
|
+
def delete_folder(self, resource_id: str) -> dict | None:
|
|
2129
|
+
"""Delete Core Share folder with a given resource ID.
|
|
2130
|
+
|
|
2131
|
+
Args:
|
|
2132
|
+
resource_id (str): ID of the folder (resource) to delete
|
|
2133
|
+
|
|
2134
|
+
Returns:
|
|
2135
|
+
dict | None: Dictionary with the Core Share request data or None if the request fails.
|
|
2136
|
+
|
|
2137
|
+
Example response (it is a list!):
|
|
2138
|
+
"""
|
|
2139
|
+
|
|
2140
|
+
if not self._access_token_user:
|
|
2141
|
+
self.authenticate_user()
|
|
2142
|
+
|
|
2143
|
+
request_header = self.request_header_user()
|
|
2144
|
+
request_url = self.config()["foldersUrlv1"] + "/{}".format(resource_id)
|
|
2145
|
+
|
|
2146
|
+
payload = {"state": "deleted"}
|
|
2147
|
+
|
|
2148
|
+
logger.debug(
|
|
2149
|
+
"Delete Core Share folder -> %s; calling -> %s",
|
|
2150
|
+
resource_id,
|
|
2151
|
+
request_url,
|
|
2152
|
+
)
|
|
2153
|
+
|
|
2154
|
+
return self.do_request(
|
|
2155
|
+
url=request_url,
|
|
2156
|
+
method="PUT",
|
|
2157
|
+
headers=request_header,
|
|
2158
|
+
data=json.dumps(payload),
|
|
2159
|
+
timeout=REQUEST_TIMEOUT,
|
|
2160
|
+
failure_message="Failed to delete Core Share folder -> {}".format(
|
|
2161
|
+
resource_id
|
|
2162
|
+
),
|
|
2163
|
+
user_credentials=True,
|
|
2164
|
+
)
|
|
2165
|
+
|
|
2166
|
+
# end method definition
|
|
2167
|
+
|
|
2168
|
+
def delete_document(self, resource_id: str) -> dict | None:
|
|
2169
|
+
"""Delete Core Share document with a given resource ID.
|
|
2170
|
+
|
|
2171
|
+
Args:
|
|
2172
|
+
resource_id (str): ID of the document (resource) to delete
|
|
2173
|
+
|
|
2174
|
+
Returns:
|
|
2175
|
+
dict | None: Dictionary with the Core Share request data or None if the request fails.
|
|
2176
|
+
|
|
2177
|
+
Example response (it is a list!):
|
|
2178
|
+
"""
|
|
2179
|
+
|
|
2180
|
+
if not self._access_token_user:
|
|
2181
|
+
self.authenticate_user()
|
|
2182
|
+
|
|
2183
|
+
request_header = self.request_header_user()
|
|
2184
|
+
request_url = self.config()["documentsUrlv1"] + "/{}".format(resource_id)
|
|
2185
|
+
|
|
2186
|
+
payload = {"state": "deleted"}
|
|
2187
|
+
|
|
2188
|
+
logger.debug(
|
|
2189
|
+
"Delete Core Share document -> %s; calling -> %s",
|
|
2190
|
+
resource_id,
|
|
2191
|
+
request_url,
|
|
2192
|
+
)
|
|
2193
|
+
|
|
2194
|
+
return self.do_request(
|
|
2195
|
+
url=request_url,
|
|
2196
|
+
method="PUT",
|
|
2197
|
+
headers=request_header,
|
|
2198
|
+
data=json.dumps(payload),
|
|
2199
|
+
timeout=REQUEST_TIMEOUT,
|
|
2200
|
+
failure_message="Failed to delete Core Share document -> {}".format(
|
|
2201
|
+
resource_id
|
|
2202
|
+
),
|
|
2203
|
+
user_credentials=True,
|
|
2204
|
+
)
|
|
2205
|
+
|
|
2206
|
+
# end method definition
|
|
2207
|
+
|
|
2208
|
+
def leave_share(self, user_id: str, resource_id: str) -> dict | None:
|
|
2209
|
+
"""Remove a Core Share user from a share (i.e. the user leaves the share)
|
|
2210
|
+
|
|
2211
|
+
Args:
|
|
2212
|
+
user_id (str): Core Share ID of the user.
|
|
2213
|
+
resource_id (str): Core Share ID of the shared folder.
|
|
2214
|
+
|
|
2215
|
+
Returns:
|
|
2216
|
+
dict | None: Reponse of the REST call or None in case of an error.
|
|
2217
|
+
"""
|
|
2218
|
+
|
|
2219
|
+
if not self._access_token_user:
|
|
2220
|
+
self.authenticate_user()
|
|
2221
|
+
|
|
2222
|
+
request_header = self.request_header_user()
|
|
2223
|
+
|
|
2224
|
+
request_url = (
|
|
2225
|
+
self.config()["foldersUrlv1"]
|
|
2226
|
+
+ "/{}".format(resource_id)
|
|
2227
|
+
+ "/collaborators/"
|
|
2228
|
+
+ str(user_id)
|
|
2229
|
+
)
|
|
2230
|
+
|
|
2231
|
+
payload = {"action": "LEAVE_SHARE"}
|
|
2232
|
+
|
|
2233
|
+
logger.debug(
|
|
2234
|
+
"User with ID -> %s leaves Core Share shared folder with ID -> %s; calling -> %s",
|
|
2235
|
+
user_id,
|
|
2236
|
+
resource_id,
|
|
2237
|
+
request_url,
|
|
2238
|
+
)
|
|
2239
|
+
|
|
2240
|
+
return self.do_request(
|
|
2241
|
+
url=request_url,
|
|
2242
|
+
method="DELETE",
|
|
2243
|
+
headers=request_header,
|
|
2244
|
+
data=json.dumps(payload),
|
|
2245
|
+
timeout=REQUEST_TIMEOUT,
|
|
2246
|
+
failure_message="User with ID -> {} failed to leave Core Share folder with ID -> {}".format(
|
|
2247
|
+
user_id, resource_id
|
|
2248
|
+
),
|
|
2249
|
+
user_credentials=True,
|
|
2250
|
+
)
|
|
2251
|
+
|
|
2252
|
+
# end method definition
|
|
2253
|
+
|
|
2254
|
+
def stop_share(self, user_id: str, resource_id: str) -> dict | None:
|
|
2255
|
+
"""Stop of share of a user.
|
|
2256
|
+
|
|
2257
|
+
Args:
|
|
2258
|
+
user_id (str): Core Share ID of the user.
|
|
2259
|
+
resource_id (str): Core Share ID of the shared folder.
|
|
2260
|
+
|
|
2261
|
+
Returns:
|
|
2262
|
+
dict | None: Response of the REST call or None in case of an error.
|
|
2263
|
+
"""
|
|
2264
|
+
|
|
2265
|
+
if not self._access_token_user:
|
|
2266
|
+
self.authenticate_user()
|
|
2267
|
+
|
|
2268
|
+
request_header = self.request_header_user()
|
|
2269
|
+
|
|
2270
|
+
request_url = (
|
|
2271
|
+
self.config()["foldersUrlv1"] + "/{}".format(resource_id) + "/collaborators"
|
|
2272
|
+
)
|
|
2273
|
+
|
|
2274
|
+
logger.debug(
|
|
2275
|
+
"User -> %s stops sharing Core Share shared folder -> %s; calling -> %s",
|
|
2276
|
+
user_id,
|
|
2277
|
+
resource_id,
|
|
2278
|
+
request_url,
|
|
2279
|
+
)
|
|
2280
|
+
|
|
2281
|
+
return self.do_request(
|
|
2282
|
+
url=request_url,
|
|
2283
|
+
method="DELETE",
|
|
2284
|
+
headers=request_header,
|
|
2285
|
+
timeout=REQUEST_TIMEOUT,
|
|
2286
|
+
failure_message="User with ID -> {} failed to stop sharing Core Share folder with ID -> {}".format(
|
|
2287
|
+
user_id, resource_id
|
|
2288
|
+
),
|
|
2289
|
+
user_credentials=True,
|
|
2290
|
+
)
|
|
2291
|
+
|
|
2292
|
+
# end method definition
|
|
2293
|
+
|
|
2294
|
+
def cleanup_user_files(
|
|
2295
|
+
self, user_id: str, user_login: str, user_password: str
|
|
2296
|
+
) -> bool:
|
|
2297
|
+
"""Cleanup files of a user. This handles different types of resources.
|
|
2298
|
+
* Local resources - not shared
|
|
2299
|
+
* Resources shared by the user
|
|
2300
|
+
* Resources shared by other users or groups
|
|
2301
|
+
This method inpersonate as the user. Only the user can delete its folders.
|
|
2302
|
+
The Core Share admin is not entitled to do this.
|
|
2303
|
+
|
|
2304
|
+
Args:
|
|
2305
|
+
user_id (str): Core Share ID of the user
|
|
2306
|
+
user_login (str): Core Share email (= login) of the user
|
|
2307
|
+
user_password (str): Core Share password of the user
|
|
2308
|
+
|
|
2309
|
+
Returns:
|
|
2310
|
+
bool: True = success, False in case of an error.
|
|
2311
|
+
"""
|
|
2312
|
+
|
|
2313
|
+
user = self.get_user_by_id(user_id=user_id)
|
|
2314
|
+
user_id = self.get_result_value(user, "id")
|
|
2315
|
+
user_root_folder_id = self.get_result_value(user, "rootId")
|
|
2316
|
+
|
|
2317
|
+
is_confirmed = self.get_result_value(response=user, key="isConfirmed")
|
|
2318
|
+
if not is_confirmed:
|
|
2319
|
+
logger.info(
|
|
2320
|
+
"User -> %s is not yet confirmed - so it cannot have files to cleanup.",
|
|
2321
|
+
user_id,
|
|
2322
|
+
)
|
|
2323
|
+
return True
|
|
2324
|
+
|
|
2325
|
+
logger.info("Inpersonate as user -> %s to cleanup files...", user_login)
|
|
2326
|
+
|
|
2327
|
+
# Save admin credentials the class has been initialized with:
|
|
2328
|
+
admin_credentials = self.credentials()
|
|
2329
|
+
|
|
2330
|
+
# Change the credentials to the user owning the file - admin
|
|
2331
|
+
# is not allowed to see user files!
|
|
2332
|
+
self.set_credentials(username=user_login, password=user_password)
|
|
2333
|
+
|
|
2334
|
+
# Authenticate as given user:
|
|
2335
|
+
self.authenticate_user(revalidate=True)
|
|
2336
|
+
|
|
2337
|
+
success = True
|
|
2338
|
+
|
|
2339
|
+
# Get all folders of the user:
|
|
2340
|
+
response = self.get_folders(parent_id=user_root_folder_id)
|
|
2341
|
+
if not response or not response["results"]:
|
|
2342
|
+
logger.info("User -> %s has no items to cleanup!", user_id)
|
|
2343
|
+
else:
|
|
2344
|
+
items = response["results"]
|
|
2345
|
+
for item in items:
|
|
2346
|
+
if item["isShared"]:
|
|
2347
|
+
if item["owner"]["id"] == user_id:
|
|
2348
|
+
logger.info(
|
|
2349
|
+
"User -> %s stops sharing item -> %s (%s)...",
|
|
2350
|
+
user_id,
|
|
2351
|
+
item["name"],
|
|
2352
|
+
item["id"],
|
|
2353
|
+
)
|
|
2354
|
+
response = self.stop_share(
|
|
2355
|
+
user_id=user_id, resource_id=item["id"]
|
|
2356
|
+
)
|
|
2357
|
+
if not response:
|
|
2358
|
+
success = False
|
|
2359
|
+
logger.info(
|
|
2360
|
+
"User -> %s deletes unshared item -> %s (%s)...",
|
|
2361
|
+
user_id,
|
|
2362
|
+
item["name"],
|
|
2363
|
+
item["id"],
|
|
2364
|
+
)
|
|
2365
|
+
response = self.delete_folder(item["id"])
|
|
2366
|
+
if not response:
|
|
2367
|
+
success = False
|
|
2368
|
+
else:
|
|
2369
|
+
logger.info(
|
|
2370
|
+
"User -> %s leaves shared folder -> '%s' (%s)...",
|
|
2371
|
+
user_id,
|
|
2372
|
+
item["name"],
|
|
2373
|
+
item["id"],
|
|
2374
|
+
)
|
|
2375
|
+
response = self.leave_share(
|
|
2376
|
+
user_id=user_id, resource_id=item["id"]
|
|
2377
|
+
)
|
|
2378
|
+
if not response:
|
|
2379
|
+
success = False
|
|
2380
|
+
else:
|
|
2381
|
+
logger.info(
|
|
2382
|
+
"User -> %s deletes local item -> '%s' (%s) of type -> '%s'...",
|
|
2383
|
+
user_id,
|
|
2384
|
+
item["name"],
|
|
2385
|
+
item["id"],
|
|
2386
|
+
item["resourceType"],
|
|
2387
|
+
)
|
|
2388
|
+
if item["resourceType"] == "folder":
|
|
2389
|
+
response = self.delete_folder(item["id"])
|
|
2390
|
+
elif item["resourceType"] == "document":
|
|
2391
|
+
response = self.delete_document(item["id"])
|
|
2392
|
+
else:
|
|
2393
|
+
logger.error(
|
|
2394
|
+
"Unsupport resource type -> '%s'", item["resourceType"]
|
|
2395
|
+
)
|
|
2396
|
+
response = None
|
|
2397
|
+
if not response:
|
|
2398
|
+
success = False
|
|
2399
|
+
|
|
2400
|
+
logger.info(
|
|
2401
|
+
"End inpersonation and switch back to admin account -> %s...",
|
|
2402
|
+
admin_credentials["username"],
|
|
2403
|
+
)
|
|
2404
|
+
|
|
2405
|
+
# Reset credentials to admin:
|
|
2406
|
+
self.set_credentials(
|
|
2407
|
+
admin_credentials["username"], admin_credentials["password"]
|
|
2408
|
+
)
|
|
2409
|
+
# Authenticate as administrator the class has been initialized with:
|
|
2410
|
+
self.authenticate_user(revalidate=True)
|
|
2411
|
+
|
|
2412
|
+
return success
|
|
2413
|
+
|
|
2414
|
+
# end method definition
|
|
2415
|
+
|
|
2416
|
+
def get_group_shares(self, group_id: str) -> dict | None:
|
|
2417
|
+
"""Get (incoming) shares of a Core Share group.
|
|
2418
|
+
|
|
2419
|
+
Args:
|
|
2420
|
+
group_id (str): Core Share ID of a group
|
|
2421
|
+
|
|
2422
|
+
Returns:
|
|
2423
|
+
dict | None: Incoming shares or None if the request fails.
|
|
2424
|
+
"""
|
|
2425
|
+
|
|
2426
|
+
if not self._access_token_admin:
|
|
2427
|
+
self.authenticate_admin()
|
|
2428
|
+
|
|
2429
|
+
request_header = self.request_header_admin()
|
|
2430
|
+
|
|
2431
|
+
request_url = (
|
|
2432
|
+
self.config()["groupsUrl"] + "/{}".format(group_id) + "/shares/incoming"
|
|
2433
|
+
)
|
|
2434
|
+
|
|
2435
|
+
logger.debug(
|
|
2436
|
+
"Get shares of Core Share group -> %s; calling -> %s",
|
|
2437
|
+
group_id,
|
|
2438
|
+
request_url,
|
|
2439
|
+
)
|
|
2440
|
+
|
|
2441
|
+
return self.do_request(
|
|
2442
|
+
url=request_url,
|
|
2443
|
+
method="GET",
|
|
2444
|
+
headers=request_header,
|
|
2445
|
+
timeout=REQUEST_TIMEOUT,
|
|
2446
|
+
failure_message="Failed to get shares of Core Share group -> {}".format(
|
|
2447
|
+
group_id
|
|
2448
|
+
),
|
|
2449
|
+
user_credentials=False,
|
|
2450
|
+
)
|
|
2451
|
+
|
|
2452
|
+
# end method definition
|
|
2453
|
+
|
|
2454
|
+
def revoke_group_share(self, group_id: str, resource_id: str) -> dict | None:
|
|
2455
|
+
"""Revoke sharing of a folder with a group.
|
|
2456
|
+
|
|
2457
|
+
Args:
|
|
2458
|
+
group_id (str): ID of the Core Share group
|
|
2459
|
+
resource_id (str): ID of the Core share folder
|
|
2460
|
+
|
|
2461
|
+
Returns:
|
|
2462
|
+
dict | None: Response or None if the request fails.
|
|
2463
|
+
"""
|
|
2464
|
+
|
|
2465
|
+
if not self._access_token_admin:
|
|
2466
|
+
self.authenticate_admin()
|
|
2467
|
+
|
|
2468
|
+
request_header = self.request_header_admin()
|
|
2469
|
+
|
|
2470
|
+
request_url = (
|
|
2471
|
+
self.config()["foldersUrlv1"]
|
|
2472
|
+
+ "/{}".format(resource_id)
|
|
2473
|
+
+ "/collaboratorsAsAdmin/"
|
|
2474
|
+
+ str(group_id)
|
|
2475
|
+
)
|
|
2476
|
+
|
|
2477
|
+
logger.debug(
|
|
2478
|
+
"Revoke sharing of folder -> %s with group -> %s; calling -> %s",
|
|
2479
|
+
resource_id,
|
|
2480
|
+
group_id,
|
|
2481
|
+
request_url,
|
|
2482
|
+
)
|
|
2483
|
+
|
|
2484
|
+
return self.do_request(
|
|
2485
|
+
url=request_url,
|
|
2486
|
+
method="DELETE",
|
|
2487
|
+
headers=request_header,
|
|
2488
|
+
timeout=REQUEST_TIMEOUT,
|
|
2489
|
+
failure_message="Failed to revoke sharing Core Share folder with ID -> {} with group with ID -> {}".format(
|
|
2490
|
+
resource_id, group_id
|
|
2491
|
+
),
|
|
2492
|
+
user_credentials=False,
|
|
2493
|
+
)
|
|
2494
|
+
|
|
2495
|
+
# end method definition
|
|
2496
|
+
|
|
2497
|
+
def cleanup_group_shares(self, group_id: str) -> bool:
|
|
2498
|
+
"""Cleanup all incoming shares of a group.
|
|
2499
|
+
The Core Share admin is required to do this.
|
|
2500
|
+
|
|
2501
|
+
Args:
|
|
2502
|
+
group_id (str): Core Share ID of the group
|
|
2503
|
+
|
|
2504
|
+
Returns:
|
|
2505
|
+
bool: True = success, False in case of an error.
|
|
2506
|
+
"""
|
|
2507
|
+
|
|
2508
|
+
response = self.get_group_shares(group_id=group_id)
|
|
2509
|
+
|
|
2510
|
+
if not response or not response["shares"]:
|
|
2511
|
+
logger.info("Group -> %s has no shares to revoke!", group_id)
|
|
2512
|
+
return True
|
|
2513
|
+
|
|
2514
|
+
success = True
|
|
2515
|
+
|
|
2516
|
+
items = response["shares"]
|
|
2517
|
+
for item in items:
|
|
2518
|
+
logger.info(
|
|
2519
|
+
"Revoke sharing of folder -> %s (%s) with group -> %s...",
|
|
2520
|
+
item["name"],
|
|
2521
|
+
item["id"],
|
|
2522
|
+
group_id,
|
|
2523
|
+
)
|
|
2524
|
+
response = self.revoke_group_share(
|
|
2525
|
+
group_id=group_id, resource_id=item["id"]
|
|
2526
|
+
)
|
|
2527
|
+
if not response:
|
|
2528
|
+
success = False
|
|
2529
|
+
|
|
2530
|
+
return success
|
|
2531
|
+
|
|
2532
|
+
# end method definition
|