pyxecm 1.3.0__py3-none-any.whl → 1.5__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 +3 -0
- pyxecm/coreshare.py +2636 -0
- pyxecm/customizer/__init__.py +6 -0
- pyxecm/customizer/browser_automation.py +231 -56
- pyxecm/customizer/customizer.py +466 -235
- pyxecm/customizer/k8s.py +49 -27
- pyxecm/customizer/m365.py +1183 -263
- pyxecm/customizer/payload.py +13854 -5368
- pyxecm/customizer/pht.py +503 -0
- pyxecm/customizer/salesforce.py +1782 -0
- pyxecm/customizer/sap.py +5 -5
- pyxecm/customizer/servicenow.py +1221 -0
- pyxecm/customizer/successfactors.py +1056 -0
- pyxecm/customizer/translate.py +2 -2
- pyxecm/helper/__init__.py +2 -0
- pyxecm/helper/assoc.py +27 -7
- pyxecm/helper/data.py +1527 -0
- pyxecm/helper/web.py +189 -25
- pyxecm/helper/xml.py +244 -40
- pyxecm/otac.py +311 -25
- pyxecm/otcs.py +3866 -1103
- pyxecm/otds.py +397 -150
- pyxecm/otiv.py +1 -1
- pyxecm/otmm.py +808 -0
- pyxecm/otpd.py +17 -12
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/METADATA +4 -1
- pyxecm-1.5.dist-info/RECORD +30 -0
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/WHEEL +1 -1
- pyxecm-1.3.0.dist-info/RECORD +0 -23
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/LICENSE +0 -0
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/top_level.txt +0 -0
pyxecm/customizer/m365.py
CHANGED
|
@@ -30,6 +30,7 @@ update_user: Update selected properties of an M365 user
|
|
|
30
30
|
get_user_licenses: Get the assigned license SKUs of a user
|
|
31
31
|
assign_license_to_user: Add an M365 license to a user (e.g. to use Office 365)
|
|
32
32
|
get_user_photo: Get the photo of a M365 user
|
|
33
|
+
download_user_photo: Download the M365 user photo and save it to the local file system
|
|
33
34
|
update_user_photo: Update a user with a profile photo (which must be in local file system)
|
|
34
35
|
|
|
35
36
|
get_groups: Get list all all groups in M365 tenant
|
|
@@ -63,6 +64,7 @@ upload_teams_app: Upload a new app package to the catalog of MS Teams apps
|
|
|
63
64
|
remove_teams_app: Remove MS Teams App for the app catalog
|
|
64
65
|
assign_teams_app_to_user: Assign (add) a MS Teams app to a M365 user.
|
|
65
66
|
upgrade_teams_app_of_user: Upgrade a MS teams app for a user.
|
|
67
|
+
remove_teams_app_from_user: Remove a M365 Teams app from a M365 user.
|
|
66
68
|
assign_teams_app_to_team: Assign (add) a MS Teams app to a M365 team
|
|
67
69
|
(so that it afterwards can be added as a Tab in a M365 Teams Channel)
|
|
68
70
|
upgrade_teams_app_of_team: Upgrade a MS teams app for a specific team.
|
|
@@ -74,10 +76,21 @@ add_sensitivity_label: Assign a existing sensitivity label to a user.
|
|
|
74
76
|
THIS IS CURRENTLY NOT WORKING!
|
|
75
77
|
assign_sensitivity_label_to_user: Create a new sensitivity label in M365
|
|
76
78
|
THIS IS CURRENTLY NOT WORKING!
|
|
79
|
+
|
|
80
|
+
upload_outlook_app: Upload the M365 Outlook Add-In as "Integrated" App to M365 Admin Center. (NOT WORKING)
|
|
81
|
+
get_app_registration: Find an Azure App Registration based on its name
|
|
82
|
+
add_app_registration: Add an Azure App Registration
|
|
83
|
+
update_app_registration: Update an Azure App Registration
|
|
84
|
+
|
|
85
|
+
get_mail: Get email from inbox of a given user and a given sender (from)
|
|
86
|
+
get_mail_body: Get full email body for a given email ID
|
|
87
|
+
extract_url_from_message_body: Parse the email body to extract (a potentially multi-line) URL from the body.
|
|
88
|
+
delete_mail: Delete email from inbox of a given user and a given email ID.
|
|
89
|
+
email_verification: Process email verification
|
|
77
90
|
"""
|
|
78
91
|
|
|
79
92
|
__author__ = "Dr. Marc Diefenbruch"
|
|
80
|
-
__copyright__ = "Copyright
|
|
93
|
+
__copyright__ = "Copyright 2024, OpenText"
|
|
81
94
|
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
82
95
|
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
83
96
|
__email__ = "mdiefenb@opentext.com"
|
|
@@ -90,9 +103,13 @@ import time
|
|
|
90
103
|
import urllib.parse
|
|
91
104
|
import zipfile
|
|
92
105
|
from urllib.parse import quote
|
|
106
|
+
from datetime import datetime
|
|
93
107
|
|
|
94
108
|
import requests
|
|
95
109
|
|
|
110
|
+
from pyxecm.helper.web import HTTP
|
|
111
|
+
from pyxecm.customizer.browser_automation import BrowserAutomation
|
|
112
|
+
|
|
96
113
|
logger = logging.getLogger("pyxecm.customizer.m365")
|
|
97
114
|
|
|
98
115
|
request_login_headers = {
|
|
@@ -100,6 +117,7 @@ request_login_headers = {
|
|
|
100
117
|
"Accept": "application/json",
|
|
101
118
|
}
|
|
102
119
|
|
|
120
|
+
REQUEST_TIMEOUT = 60
|
|
103
121
|
|
|
104
122
|
class M365(object):
|
|
105
123
|
"""Used to automate stettings in Microsoft 365 via the Graph API."""
|
|
@@ -107,6 +125,7 @@ class M365(object):
|
|
|
107
125
|
_config: dict
|
|
108
126
|
_access_token = None
|
|
109
127
|
_user_access_token = None
|
|
128
|
+
_http_object: HTTP | None = None
|
|
110
129
|
|
|
111
130
|
def __init__(
|
|
112
131
|
self,
|
|
@@ -116,6 +135,7 @@ class M365(object):
|
|
|
116
135
|
domain: str,
|
|
117
136
|
sku_id: str,
|
|
118
137
|
teams_app_name: str,
|
|
138
|
+
teams_app_external_id: str,
|
|
119
139
|
):
|
|
120
140
|
"""Initialize the M365 object
|
|
121
141
|
|
|
@@ -126,6 +146,7 @@ class M365(object):
|
|
|
126
146
|
domain (str): M365 domain
|
|
127
147
|
sku_id (str): License SKU for M365 users
|
|
128
148
|
teams_app_name (str): name of the Extended ECM app for MS Teams
|
|
149
|
+
teams_app_external_id (str): external ID of the Extended ECM app for MS Teams
|
|
129
150
|
"""
|
|
130
151
|
|
|
131
152
|
m365_config = {}
|
|
@@ -137,6 +158,10 @@ class M365(object):
|
|
|
137
158
|
m365_config["domain"] = domain
|
|
138
159
|
m365_config["skuId"] = sku_id
|
|
139
160
|
m365_config["teamsAppName"] = teams_app_name
|
|
161
|
+
m365_config["teamsAppExternalId"] = (
|
|
162
|
+
teams_app_external_id # this is the external App ID
|
|
163
|
+
)
|
|
164
|
+
m365_config["teamsAppInternalId"] = None # will be set later...
|
|
140
165
|
m365_config[
|
|
141
166
|
"authenticationUrl"
|
|
142
167
|
] = "https://login.microsoftonline.com/{}/oauth2/v2.0/token".format(tenant_id)
|
|
@@ -162,6 +187,7 @@ class M365(object):
|
|
|
162
187
|
m365_config["applicationsUrl"] = m365_config["graphUrl"] + "applications"
|
|
163
188
|
|
|
164
189
|
self._config = m365_config
|
|
190
|
+
self._http_object = HTTP()
|
|
165
191
|
|
|
166
192
|
def config(self) -> dict:
|
|
167
193
|
"""Returns the configuration dictionary
|
|
@@ -396,12 +422,16 @@ class M365(object):
|
|
|
396
422
|
|
|
397
423
|
# Already authenticated and session still valid?
|
|
398
424
|
if self._access_token and not revalidate:
|
|
425
|
+
logger.debug(
|
|
426
|
+
"Session still valid - return existing access token -> %s",
|
|
427
|
+
str(self._access_token),
|
|
428
|
+
)
|
|
399
429
|
return self._access_token
|
|
400
430
|
|
|
401
431
|
request_url = self.config()["authenticationUrl"]
|
|
402
432
|
request_header = request_login_headers
|
|
403
433
|
|
|
404
|
-
logger.
|
|
434
|
+
logger.debug("Requesting M365 Access Token from -> %s", request_url)
|
|
405
435
|
|
|
406
436
|
authenticate_post_body = self.credentials()
|
|
407
437
|
authenticate_response = None
|
|
@@ -411,7 +441,7 @@ class M365(object):
|
|
|
411
441
|
request_url,
|
|
412
442
|
data=authenticate_post_body,
|
|
413
443
|
headers=request_header,
|
|
414
|
-
timeout=
|
|
444
|
+
timeout=REQUEST_TIMEOUT,
|
|
415
445
|
)
|
|
416
446
|
except requests.exceptions.ConnectionError as exception:
|
|
417
447
|
logger.warning(
|
|
@@ -437,6 +467,7 @@ class M365(object):
|
|
|
437
467
|
|
|
438
468
|
# Store authentication access_token:
|
|
439
469
|
self._access_token = access_token
|
|
470
|
+
|
|
440
471
|
return self._access_token
|
|
441
472
|
|
|
442
473
|
# end method definition
|
|
@@ -454,7 +485,7 @@ class M365(object):
|
|
|
454
485
|
request_url = self.config()["authenticationUrl"]
|
|
455
486
|
request_header = request_login_headers
|
|
456
487
|
|
|
457
|
-
logger.
|
|
488
|
+
logger.debug(
|
|
458
489
|
"Requesting M365 Access Token for user -> %s from -> %s",
|
|
459
490
|
username,
|
|
460
491
|
request_url,
|
|
@@ -468,7 +499,7 @@ class M365(object):
|
|
|
468
499
|
request_url,
|
|
469
500
|
data=authenticate_post_body,
|
|
470
501
|
headers=request_header,
|
|
471
|
-
timeout=
|
|
502
|
+
timeout=REQUEST_TIMEOUT,
|
|
472
503
|
)
|
|
473
504
|
except requests.exceptions.ConnectionError as exception:
|
|
474
505
|
logger.warning(
|
|
@@ -495,6 +526,7 @@ class M365(object):
|
|
|
495
526
|
|
|
496
527
|
# Store authentication access_token:
|
|
497
528
|
self._user_access_token = access_token
|
|
529
|
+
|
|
498
530
|
return self._user_access_token
|
|
499
531
|
|
|
500
532
|
# end method definition
|
|
@@ -509,17 +541,19 @@ class M365(object):
|
|
|
509
541
|
request_url = self.config()["usersUrl"]
|
|
510
542
|
request_header = self.request_header()
|
|
511
543
|
|
|
512
|
-
logger.
|
|
544
|
+
logger.debug("Get list of all users; calling -> %s", request_url)
|
|
513
545
|
|
|
514
546
|
retries = 0
|
|
515
547
|
while True:
|
|
516
|
-
response = requests.get(
|
|
548
|
+
response = requests.get(
|
|
549
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
550
|
+
)
|
|
517
551
|
if response.ok:
|
|
518
552
|
return self.parse_request_response(response)
|
|
519
553
|
# Check if Session has expired - then re-authenticate and try once more
|
|
520
554
|
elif response.status_code == 401 and retries == 0:
|
|
521
|
-
logger.
|
|
522
|
-
self.authenticate(True)
|
|
555
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
556
|
+
self.authenticate(revalidate=True)
|
|
523
557
|
request_header = self.request_header()
|
|
524
558
|
retries += 1
|
|
525
559
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -567,20 +601,47 @@ class M365(object):
|
|
|
567
601
|
}
|
|
568
602
|
"""
|
|
569
603
|
|
|
604
|
+
# Some sanity checks:
|
|
605
|
+
if not "@" in user_email or not "." in user_email:
|
|
606
|
+
logger.error("User email -> %s is not a valid email address", user_email)
|
|
607
|
+
return None
|
|
608
|
+
|
|
609
|
+
# if there's an alias in the E-Mail Adress we remove it as
|
|
610
|
+
# MS Graph seems to not support an alias to lookup a user object.
|
|
611
|
+
if "+" in user_email:
|
|
612
|
+
logger.info(
|
|
613
|
+
"Removing Alias from email address -> %s to determine M365 principal name...",
|
|
614
|
+
user_email,
|
|
615
|
+
)
|
|
616
|
+
# Find the index of the '+' character
|
|
617
|
+
alias_index = user_email.find("+")
|
|
618
|
+
|
|
619
|
+
# Find the index of the '@' character
|
|
620
|
+
domain_index = user_email.find("@")
|
|
621
|
+
|
|
622
|
+
# Construct the email address without the alias
|
|
623
|
+
user_email = user_email[:alias_index] + user_email[domain_index:]
|
|
624
|
+
logger.info(
|
|
625
|
+
"M365 user principal name -> %s",
|
|
626
|
+
user_email,
|
|
627
|
+
)
|
|
628
|
+
|
|
570
629
|
request_url = self.config()["usersUrl"] + "/" + user_email
|
|
571
630
|
request_header = self.request_header()
|
|
572
631
|
|
|
573
|
-
logger.
|
|
632
|
+
logger.debug("Get M365 user -> %s; calling -> %s", user_email, request_url)
|
|
574
633
|
|
|
575
634
|
retries = 0
|
|
576
635
|
while True:
|
|
577
|
-
response = requests.get(
|
|
636
|
+
response = requests.get(
|
|
637
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
638
|
+
)
|
|
578
639
|
if response.ok:
|
|
579
640
|
return self.parse_request_response(response)
|
|
580
641
|
# Check if Session has expired - then re-authenticate and try once more
|
|
581
642
|
elif response.status_code == 401 and retries == 0:
|
|
582
|
-
logger.
|
|
583
|
-
self.authenticate(True)
|
|
643
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
644
|
+
self.authenticate(revalidate=True)
|
|
584
645
|
request_header = self.request_header()
|
|
585
646
|
retries += 1
|
|
586
647
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -600,7 +661,7 @@ class M365(object):
|
|
|
600
661
|
response.text,
|
|
601
662
|
)
|
|
602
663
|
else:
|
|
603
|
-
logger.
|
|
664
|
+
logger.debug("M365 User -> %s not found.", user_email)
|
|
604
665
|
return None
|
|
605
666
|
|
|
606
667
|
# end method definition
|
|
@@ -651,7 +712,7 @@ class M365(object):
|
|
|
651
712
|
request_url = self.config()["usersUrl"]
|
|
652
713
|
request_header = self.request_header()
|
|
653
714
|
|
|
654
|
-
logger.
|
|
715
|
+
logger.debug("Adding M365 user -> %s; calling -> %s", email, request_url)
|
|
655
716
|
|
|
656
717
|
retries = 0
|
|
657
718
|
while True:
|
|
@@ -659,14 +720,14 @@ class M365(object):
|
|
|
659
720
|
request_url,
|
|
660
721
|
data=json.dumps(user_post_body),
|
|
661
722
|
headers=request_header,
|
|
662
|
-
timeout=
|
|
723
|
+
timeout=REQUEST_TIMEOUT,
|
|
663
724
|
)
|
|
664
725
|
if response.ok:
|
|
665
726
|
return self.parse_request_response(response)
|
|
666
727
|
# Check if Session has expired - then re-authenticate and try once more
|
|
667
728
|
elif response.status_code == 401 and retries == 0:
|
|
668
|
-
logger.
|
|
669
|
-
self.authenticate(True)
|
|
729
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
730
|
+
self.authenticate(revalidate=True)
|
|
670
731
|
request_header = self.request_header()
|
|
671
732
|
retries += 1
|
|
672
733
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -692,6 +753,9 @@ class M365(object):
|
|
|
692
753
|
"""Update selected properties of an M365 user. Documentation
|
|
693
754
|
on user properties is here: https://learn.microsoft.com/en-us/graph/api/user-update
|
|
694
755
|
|
|
756
|
+
Args:
|
|
757
|
+
user_id (str): ID of the user (can also be email). This is also the unique identifier
|
|
758
|
+
updated_settings (dict): new data to update the user with
|
|
695
759
|
Returns:
|
|
696
760
|
dict | None: Response of the M365 Graph API or None if the call fails.
|
|
697
761
|
"""
|
|
@@ -699,8 +763,8 @@ class M365(object):
|
|
|
699
763
|
request_url = self.config()["usersUrl"] + "/" + user_id
|
|
700
764
|
request_header = self.request_header()
|
|
701
765
|
|
|
702
|
-
logger.
|
|
703
|
-
"Updating M365 user -> %s with -> %s; calling -> %s",
|
|
766
|
+
logger.debug(
|
|
767
|
+
"Updating M365 user with ID -> %s with -> %s; calling -> %s",
|
|
704
768
|
user_id,
|
|
705
769
|
str(updated_settings),
|
|
706
770
|
request_url,
|
|
@@ -712,14 +776,14 @@ class M365(object):
|
|
|
712
776
|
request_url,
|
|
713
777
|
json=updated_settings,
|
|
714
778
|
headers=request_header,
|
|
715
|
-
timeout=
|
|
779
|
+
timeout=REQUEST_TIMEOUT,
|
|
716
780
|
)
|
|
717
781
|
if response.ok:
|
|
718
782
|
return self.parse_request_response(response)
|
|
719
783
|
# Check if Session has expired - then re-authenticate and try once more
|
|
720
784
|
elif response.status_code == 401 and retries == 0:
|
|
721
|
-
logger.
|
|
722
|
-
self.authenticate(True)
|
|
785
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
786
|
+
self.authenticate(revalidate=True)
|
|
723
787
|
request_header = self.request_header()
|
|
724
788
|
retries += 1
|
|
725
789
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -769,13 +833,15 @@ class M365(object):
|
|
|
769
833
|
|
|
770
834
|
retries = 0
|
|
771
835
|
while True:
|
|
772
|
-
response = requests.get(
|
|
836
|
+
response = requests.get(
|
|
837
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
838
|
+
)
|
|
773
839
|
if response.ok:
|
|
774
840
|
return self.parse_request_response(response)
|
|
775
841
|
# Check if Session has expired - then re-authenticate and try once more
|
|
776
842
|
elif response.status_code == 401 and retries == 0:
|
|
777
|
-
logger.
|
|
778
|
-
self.authenticate(True)
|
|
843
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
844
|
+
self.authenticate(revalidate=True)
|
|
779
845
|
request_header = self.request_header()
|
|
780
846
|
retries += 1
|
|
781
847
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -824,7 +890,7 @@ class M365(object):
|
|
|
824
890
|
"removeLicenses": [],
|
|
825
891
|
}
|
|
826
892
|
|
|
827
|
-
logger.
|
|
893
|
+
logger.debug(
|
|
828
894
|
"Assign M365 license -> %s to M365 user -> %s; calling -> %s",
|
|
829
895
|
sku_id,
|
|
830
896
|
user_id,
|
|
@@ -834,14 +900,17 @@ class M365(object):
|
|
|
834
900
|
retries = 0
|
|
835
901
|
while True:
|
|
836
902
|
response = requests.post(
|
|
837
|
-
request_url,
|
|
903
|
+
request_url,
|
|
904
|
+
json=license_post_body,
|
|
905
|
+
headers=request_header,
|
|
906
|
+
timeout=REQUEST_TIMEOUT,
|
|
838
907
|
)
|
|
839
908
|
if response.ok:
|
|
840
909
|
return self.parse_request_response(response)
|
|
841
910
|
# Check if Session has expired - then re-authenticate and try once more
|
|
842
911
|
elif response.status_code == 401 and retries == 0:
|
|
843
|
-
logger.
|
|
844
|
-
self.authenticate(True)
|
|
912
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
913
|
+
self.authenticate(revalidate=True)
|
|
845
914
|
request_header = self.request_header()
|
|
846
915
|
retries += 1
|
|
847
916
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -879,17 +948,19 @@ class M365(object):
|
|
|
879
948
|
# Set image as content type:
|
|
880
949
|
request_header = self.request_header("image/*")
|
|
881
950
|
|
|
882
|
-
logger.
|
|
951
|
+
logger.debug("Get photo of user -> %s; calling -> %s", user_id, request_url)
|
|
883
952
|
|
|
884
953
|
retries = 0
|
|
885
954
|
while True:
|
|
886
|
-
response = requests.get(
|
|
955
|
+
response = requests.get(
|
|
956
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
957
|
+
)
|
|
887
958
|
if response.ok:
|
|
888
959
|
return response.content # this is the actual image - not json!
|
|
889
960
|
# Check if Session has expired - then re-authenticate and try once more
|
|
890
961
|
elif response.status_code == 401 and retries == 0:
|
|
891
|
-
logger.
|
|
892
|
-
self.authenticate(True)
|
|
962
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
963
|
+
self.authenticate(revalidate=True)
|
|
893
964
|
request_header = self.request_header()
|
|
894
965
|
retries += 1
|
|
895
966
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -909,7 +980,88 @@ class M365(object):
|
|
|
909
980
|
response.text,
|
|
910
981
|
)
|
|
911
982
|
else:
|
|
912
|
-
logger.
|
|
983
|
+
logger.debug("M365 User -> %s does not yet have a photo.", user_id)
|
|
984
|
+
return None
|
|
985
|
+
|
|
986
|
+
# end method definition
|
|
987
|
+
|
|
988
|
+
def download_user_photo(self, user_id: str, photo_path: str) -> str:
|
|
989
|
+
"""Download the M365 user photo and save it to the local file system
|
|
990
|
+
|
|
991
|
+
Args:
|
|
992
|
+
user_id (str): M365 GUID of the user (can also be the M365 email of the user)
|
|
993
|
+
photo_path (str): Directory where the photo should be saved
|
|
994
|
+
Returns:
|
|
995
|
+
str: name of the photo file in the file system (with full path) or None if
|
|
996
|
+
the call of the REST API fails.
|
|
997
|
+
"""
|
|
998
|
+
|
|
999
|
+
request_url = self.config()["usersUrl"] + "/" + user_id + "/photo/$value"
|
|
1000
|
+
request_header = self.request_header("application/json")
|
|
1001
|
+
|
|
1002
|
+
logger.debug(
|
|
1003
|
+
"Downloading photo for M365 user with ID -> %s; calling -> %s",
|
|
1004
|
+
user_id,
|
|
1005
|
+
request_url,
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
retries = 0
|
|
1009
|
+
while True:
|
|
1010
|
+
response = requests.get(
|
|
1011
|
+
request_url,
|
|
1012
|
+
headers=request_header,
|
|
1013
|
+
timeout=REQUEST_TIMEOUT,
|
|
1014
|
+
stream=True,
|
|
1015
|
+
)
|
|
1016
|
+
if response.ok:
|
|
1017
|
+
content_type = response.headers.get("Content-Type", "image/png")
|
|
1018
|
+
if content_type == "image/jpeg":
|
|
1019
|
+
file_extension = "jpg"
|
|
1020
|
+
elif content_type == "image/png":
|
|
1021
|
+
file_extension = "png"
|
|
1022
|
+
else:
|
|
1023
|
+
file_extension = "img" # Default extension if type is unknown
|
|
1024
|
+
file_path = os.path.join(
|
|
1025
|
+
photo_path, "{}.{}".format(user_id, file_extension)
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
try:
|
|
1029
|
+
with open(file_path, "wb") as file:
|
|
1030
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
1031
|
+
file.write(chunk)
|
|
1032
|
+
logger.info(
|
|
1033
|
+
"Photo for M365 user with ID -> %s saved to %s",
|
|
1034
|
+
user_id,
|
|
1035
|
+
file_path,
|
|
1036
|
+
)
|
|
1037
|
+
return file_path
|
|
1038
|
+
except OSError as exception:
|
|
1039
|
+
logger.error(
|
|
1040
|
+
"Error saving photo for user with ID -> %s; error -> %s",
|
|
1041
|
+
user_id,
|
|
1042
|
+
exception,
|
|
1043
|
+
)
|
|
1044
|
+
return None
|
|
1045
|
+
elif response.status_code == 401 and retries == 0:
|
|
1046
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1047
|
+
self.authenticate(revalidate=True)
|
|
1048
|
+
request_header = self.request_header("application/json")
|
|
1049
|
+
retries += 1
|
|
1050
|
+
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
1051
|
+
logger.warning(
|
|
1052
|
+
"M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
|
|
1053
|
+
response.status_code,
|
|
1054
|
+
(retries + 1) * 60,
|
|
1055
|
+
)
|
|
1056
|
+
time.sleep((retries + 1) * 60)
|
|
1057
|
+
retries += 1
|
|
1058
|
+
else:
|
|
1059
|
+
logger.error(
|
|
1060
|
+
"Failed to download photo for user with ID -> %s; status -> %s; error -> %s",
|
|
1061
|
+
user_id,
|
|
1062
|
+
response.status_code,
|
|
1063
|
+
response.text,
|
|
1064
|
+
)
|
|
913
1065
|
return None
|
|
914
1066
|
|
|
915
1067
|
# end method definition
|
|
@@ -946,8 +1098,8 @@ class M365(object):
|
|
|
946
1098
|
|
|
947
1099
|
data = photo_data
|
|
948
1100
|
|
|
949
|
-
logger.
|
|
950
|
-
"Update M365 user -> %s with photo -> %s; calling -> %s",
|
|
1101
|
+
logger.debug(
|
|
1102
|
+
"Update M365 user with ID -> %s with photo -> %s; calling -> %s",
|
|
951
1103
|
user_id,
|
|
952
1104
|
photo_path,
|
|
953
1105
|
request_url,
|
|
@@ -956,14 +1108,14 @@ class M365(object):
|
|
|
956
1108
|
retries = 0
|
|
957
1109
|
while True:
|
|
958
1110
|
response = requests.put(
|
|
959
|
-
request_url, headers=request_header, data=data, timeout=
|
|
1111
|
+
request_url, headers=request_header, data=data, timeout=REQUEST_TIMEOUT
|
|
960
1112
|
)
|
|
961
1113
|
if response.ok:
|
|
962
1114
|
return self.parse_request_response(response)
|
|
963
1115
|
# Check if Session has expired - then re-authenticate and try once more
|
|
964
1116
|
elif response.status_code == 401 and retries == 0:
|
|
965
|
-
logger.
|
|
966
|
-
self.authenticate(True)
|
|
1117
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1118
|
+
self.authenticate(revalidate=True)
|
|
967
1119
|
request_header = self.request_header()
|
|
968
1120
|
retries += 1
|
|
969
1121
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -976,7 +1128,7 @@ class M365(object):
|
|
|
976
1128
|
retries += 1
|
|
977
1129
|
else:
|
|
978
1130
|
logger.error(
|
|
979
|
-
"Failed to update user -> %s with photo -> %s; status -> %s; error -> %s",
|
|
1131
|
+
"Failed to update user with ID -> %s with photo -> %s; status -> %s; error -> %s",
|
|
980
1132
|
user_id,
|
|
981
1133
|
photo_path,
|
|
982
1134
|
response.status_code,
|
|
@@ -998,7 +1150,7 @@ class M365(object):
|
|
|
998
1150
|
request_url = self.config()["groupsUrl"]
|
|
999
1151
|
request_header = self.request_header()
|
|
1000
1152
|
|
|
1001
|
-
logger.
|
|
1153
|
+
logger.debug("Get list of all M365 groups; calling -> %s", request_url)
|
|
1002
1154
|
|
|
1003
1155
|
retries = 0
|
|
1004
1156
|
while True:
|
|
@@ -1006,14 +1158,14 @@ class M365(object):
|
|
|
1006
1158
|
request_url,
|
|
1007
1159
|
headers=request_header,
|
|
1008
1160
|
params={"$top": str(max_number)},
|
|
1009
|
-
timeout=
|
|
1161
|
+
timeout=REQUEST_TIMEOUT,
|
|
1010
1162
|
)
|
|
1011
1163
|
if response.ok:
|
|
1012
1164
|
return self.parse_request_response(response)
|
|
1013
1165
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1014
1166
|
elif response.status_code == 401 and retries == 0:
|
|
1015
|
-
logger.
|
|
1016
|
-
self.authenticate(True)
|
|
1167
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1168
|
+
self.authenticate(revalidate=True)
|
|
1017
1169
|
request_header = self.request_header()
|
|
1018
1170
|
retries += 1
|
|
1019
1171
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1095,17 +1247,19 @@ class M365(object):
|
|
|
1095
1247
|
request_url = self.config()["groupsUrl"] + "?" + encoded_query
|
|
1096
1248
|
request_header = self.request_header()
|
|
1097
1249
|
|
|
1098
|
-
logger.
|
|
1250
|
+
logger.debug("Get M365 group -> %s; calling -> %s", group_name, request_url)
|
|
1099
1251
|
|
|
1100
1252
|
retries = 0
|
|
1101
1253
|
while True:
|
|
1102
|
-
response = requests.get(
|
|
1254
|
+
response = requests.get(
|
|
1255
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
1256
|
+
)
|
|
1103
1257
|
if response.ok:
|
|
1104
1258
|
return self.parse_request_response(response)
|
|
1105
1259
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1106
1260
|
elif response.status_code == 401 and retries == 0:
|
|
1107
|
-
logger.
|
|
1108
|
-
self.authenticate(True)
|
|
1261
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1262
|
+
self.authenticate(revalidate=True)
|
|
1109
1263
|
request_header = self.request_header()
|
|
1110
1264
|
retries += 1
|
|
1111
1265
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1125,7 +1279,7 @@ class M365(object):
|
|
|
1125
1279
|
response.text,
|
|
1126
1280
|
)
|
|
1127
1281
|
else:
|
|
1128
|
-
logger.
|
|
1282
|
+
logger.debug("M365 Group -> %s not found.", group_name)
|
|
1129
1283
|
return None
|
|
1130
1284
|
|
|
1131
1285
|
# end method definition
|
|
@@ -1191,7 +1345,7 @@ class M365(object):
|
|
|
1191
1345
|
request_url = self.config()["groupsUrl"]
|
|
1192
1346
|
request_header = self.request_header()
|
|
1193
1347
|
|
|
1194
|
-
logger.
|
|
1348
|
+
logger.debug("Adding M365 group -> %s; calling -> %s", name, request_url)
|
|
1195
1349
|
logger.debug("M365 group attributes -> %s", group_post_body)
|
|
1196
1350
|
|
|
1197
1351
|
retries = 0
|
|
@@ -1200,14 +1354,14 @@ class M365(object):
|
|
|
1200
1354
|
request_url,
|
|
1201
1355
|
data=json.dumps(group_post_body),
|
|
1202
1356
|
headers=request_header,
|
|
1203
|
-
timeout=
|
|
1357
|
+
timeout=REQUEST_TIMEOUT,
|
|
1204
1358
|
)
|
|
1205
1359
|
if response.ok:
|
|
1206
1360
|
return self.parse_request_response(response)
|
|
1207
1361
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1208
1362
|
elif response.status_code == 401 and retries == 0:
|
|
1209
|
-
logger.
|
|
1210
|
-
self.authenticate(True)
|
|
1363
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1364
|
+
self.authenticate(revalidate=True)
|
|
1211
1365
|
request_header = self.request_header()
|
|
1212
1366
|
retries += 1
|
|
1213
1367
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1255,7 +1409,7 @@ class M365(object):
|
|
|
1255
1409
|
)
|
|
1256
1410
|
request_header = self.request_header()
|
|
1257
1411
|
|
|
1258
|
-
logger.
|
|
1412
|
+
logger.debug(
|
|
1259
1413
|
"Get members of M365 group -> %s (%s); calling -> %s",
|
|
1260
1414
|
group_name,
|
|
1261
1415
|
group_id,
|
|
@@ -1264,13 +1418,15 @@ class M365(object):
|
|
|
1264
1418
|
|
|
1265
1419
|
retries = 0
|
|
1266
1420
|
while True:
|
|
1267
|
-
response = requests.get(
|
|
1421
|
+
response = requests.get(
|
|
1422
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
1423
|
+
)
|
|
1268
1424
|
if response.ok:
|
|
1269
1425
|
return self.parse_request_response(response)
|
|
1270
1426
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1271
1427
|
elif response.status_code == 401 and retries == 0:
|
|
1272
|
-
logger.
|
|
1273
|
-
self.authenticate(True)
|
|
1428
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1429
|
+
self.authenticate(revalidate=True)
|
|
1274
1430
|
request_header = self.request_header()
|
|
1275
1431
|
retries += 1
|
|
1276
1432
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1310,7 +1466,7 @@ class M365(object):
|
|
|
1310
1466
|
"@odata.id": self.config()["directoryObjects"] + "/" + member_id
|
|
1311
1467
|
}
|
|
1312
1468
|
|
|
1313
|
-
logger.
|
|
1469
|
+
logger.debug(
|
|
1314
1470
|
"Adding member -> %s to group -> %s; calling -> %s",
|
|
1315
1471
|
member_id,
|
|
1316
1472
|
group_id,
|
|
@@ -1323,15 +1479,15 @@ class M365(object):
|
|
|
1323
1479
|
request_url,
|
|
1324
1480
|
headers=request_header,
|
|
1325
1481
|
data=json.dumps(group_member_post_body),
|
|
1326
|
-
timeout=
|
|
1482
|
+
timeout=REQUEST_TIMEOUT,
|
|
1327
1483
|
)
|
|
1328
1484
|
if response.ok:
|
|
1329
1485
|
return self.parse_request_response(response)
|
|
1330
1486
|
|
|
1331
1487
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1332
1488
|
if response.status_code == 401 and retries == 0:
|
|
1333
|
-
logger.
|
|
1334
|
-
self.authenticate(True)
|
|
1489
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1490
|
+
self.authenticate(revalidate=True)
|
|
1335
1491
|
request_header = self.request_header()
|
|
1336
1492
|
retries += 1
|
|
1337
1493
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1373,7 +1529,7 @@ class M365(object):
|
|
|
1373
1529
|
)
|
|
1374
1530
|
request_header = self.request_header()
|
|
1375
1531
|
|
|
1376
|
-
logger.
|
|
1532
|
+
logger.debug(
|
|
1377
1533
|
"Check if user -> %s is in group -> %s; calling -> %s",
|
|
1378
1534
|
member_id,
|
|
1379
1535
|
group_id,
|
|
@@ -1382,7 +1538,9 @@ class M365(object):
|
|
|
1382
1538
|
|
|
1383
1539
|
retries = 0
|
|
1384
1540
|
while True:
|
|
1385
|
-
response = requests.get(
|
|
1541
|
+
response = requests.get(
|
|
1542
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
1543
|
+
)
|
|
1386
1544
|
if response.ok:
|
|
1387
1545
|
response = self.parse_request_response(response)
|
|
1388
1546
|
if not "value" in response or len(response["value"]) == 0:
|
|
@@ -1390,8 +1548,8 @@ class M365(object):
|
|
|
1390
1548
|
return True
|
|
1391
1549
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1392
1550
|
elif response.status_code == 401 and retries == 0:
|
|
1393
|
-
logger.
|
|
1394
|
-
self.authenticate(True)
|
|
1551
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1552
|
+
self.authenticate(revalidate=True)
|
|
1395
1553
|
request_header = self.request_header()
|
|
1396
1554
|
retries += 1
|
|
1397
1555
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1443,7 +1601,7 @@ class M365(object):
|
|
|
1443
1601
|
)
|
|
1444
1602
|
request_header = self.request_header()
|
|
1445
1603
|
|
|
1446
|
-
logger.
|
|
1604
|
+
logger.debug(
|
|
1447
1605
|
"Get owners of M365 group -> %s (%s); calling -> %s",
|
|
1448
1606
|
group_name,
|
|
1449
1607
|
group_id,
|
|
@@ -1452,13 +1610,15 @@ class M365(object):
|
|
|
1452
1610
|
|
|
1453
1611
|
retries = 0
|
|
1454
1612
|
while True:
|
|
1455
|
-
response = requests.get(
|
|
1613
|
+
response = requests.get(
|
|
1614
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
1615
|
+
)
|
|
1456
1616
|
if response.ok:
|
|
1457
1617
|
return self.parse_request_response(response)
|
|
1458
1618
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1459
1619
|
elif response.status_code == 401 and retries == 0:
|
|
1460
|
-
logger.
|
|
1461
|
-
self.authenticate(True)
|
|
1620
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1621
|
+
self.authenticate(revalidate=True)
|
|
1462
1622
|
request_header = self.request_header()
|
|
1463
1623
|
retries += 1
|
|
1464
1624
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1498,7 +1658,7 @@ class M365(object):
|
|
|
1498
1658
|
"@odata.id": self.config()["directoryObjects"] + "/" + owner_id
|
|
1499
1659
|
}
|
|
1500
1660
|
|
|
1501
|
-
logger.
|
|
1661
|
+
logger.debug(
|
|
1502
1662
|
"Adding owner -> %s to M365 group -> %s; calling -> %s",
|
|
1503
1663
|
owner_id,
|
|
1504
1664
|
group_id,
|
|
@@ -1511,14 +1671,14 @@ class M365(object):
|
|
|
1511
1671
|
request_url,
|
|
1512
1672
|
headers=request_header,
|
|
1513
1673
|
data=json.dumps(group_member_post_body),
|
|
1514
|
-
timeout=
|
|
1674
|
+
timeout=REQUEST_TIMEOUT,
|
|
1515
1675
|
)
|
|
1516
1676
|
if response.ok:
|
|
1517
1677
|
return self.parse_request_response(response)
|
|
1518
1678
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1519
1679
|
elif response.status_code == 401 and retries == 0:
|
|
1520
|
-
logger.
|
|
1521
|
-
self.authenticate(True)
|
|
1680
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1681
|
+
self.authenticate(revalidate=True)
|
|
1522
1682
|
request_header = self.request_header()
|
|
1523
1683
|
retries += 1
|
|
1524
1684
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1552,7 +1712,9 @@ class M365(object):
|
|
|
1552
1712
|
request_url = (
|
|
1553
1713
|
self.config()["directoryUrl"] + "/deletedItems/microsoft.graph.group"
|
|
1554
1714
|
)
|
|
1555
|
-
response = requests.get(
|
|
1715
|
+
response = requests.get(
|
|
1716
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
1717
|
+
)
|
|
1556
1718
|
deleted_groups = self.parse_request_response(response)
|
|
1557
1719
|
|
|
1558
1720
|
for group in deleted_groups["value"]:
|
|
@@ -1562,7 +1724,9 @@ class M365(object):
|
|
|
1562
1724
|
request_url = (
|
|
1563
1725
|
self.config()["directoryUrl"] + "/deletedItems/microsoft.graph.user"
|
|
1564
1726
|
)
|
|
1565
|
-
response = requests.get(
|
|
1727
|
+
response = requests.get(
|
|
1728
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
1729
|
+
)
|
|
1566
1730
|
deleted_users = self.parse_request_response(response)
|
|
1567
1731
|
|
|
1568
1732
|
for user in deleted_users["value"]:
|
|
@@ -1585,17 +1749,19 @@ class M365(object):
|
|
|
1585
1749
|
request_url = self.config()["directoryUrl"] + "/deletedItems/" + item_id
|
|
1586
1750
|
request_header = self.request_header()
|
|
1587
1751
|
|
|
1588
|
-
logger.
|
|
1752
|
+
logger.debug("Purging deleted item -> %s; calling -> %s", item_id, request_url)
|
|
1589
1753
|
|
|
1590
1754
|
retries = 0
|
|
1591
1755
|
while True:
|
|
1592
|
-
response = requests.delete(
|
|
1756
|
+
response = requests.delete(
|
|
1757
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
1758
|
+
)
|
|
1593
1759
|
if response.ok:
|
|
1594
1760
|
return self.parse_request_response(response)
|
|
1595
1761
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1596
1762
|
elif response.status_code == 401 and retries == 0:
|
|
1597
|
-
logger.
|
|
1598
|
-
self.authenticate(True)
|
|
1763
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1764
|
+
self.authenticate(revalidate=True)
|
|
1599
1765
|
request_header = self.request_header()
|
|
1600
1766
|
retries += 1
|
|
1601
1767
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1638,7 +1804,7 @@ class M365(object):
|
|
|
1638
1804
|
request_url = self.config()["groupsUrl"] + "/" + group_id + "/team"
|
|
1639
1805
|
request_header = self.request_header()
|
|
1640
1806
|
|
|
1641
|
-
logger.
|
|
1807
|
+
logger.debug(
|
|
1642
1808
|
"Check if M365 Group -> %s has a M365 Team connected; calling -> %s",
|
|
1643
1809
|
group_name,
|
|
1644
1810
|
request_url,
|
|
@@ -1646,17 +1812,19 @@ class M365(object):
|
|
|
1646
1812
|
|
|
1647
1813
|
retries = 0
|
|
1648
1814
|
while True:
|
|
1649
|
-
response = requests.get(
|
|
1815
|
+
response = requests.get(
|
|
1816
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
1817
|
+
)
|
|
1650
1818
|
|
|
1651
1819
|
if response.status_code == 200: # Group has a Team assigned!
|
|
1652
|
-
logger.
|
|
1820
|
+
logger.debug("Group -> %s has a M365 Team connected.", group_name)
|
|
1653
1821
|
return True
|
|
1654
1822
|
elif response.status_code == 404: # Group does not have a Team assigned!
|
|
1655
|
-
logger.
|
|
1823
|
+
logger.debug("Group -> %s has no M365 Team connected.", group_name)
|
|
1656
1824
|
return False
|
|
1657
1825
|
elif response.status_code == 401 and retries == 0:
|
|
1658
|
-
logger.
|
|
1659
|
-
self.authenticate(True)
|
|
1826
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1827
|
+
self.authenticate(revalidate=True)
|
|
1660
1828
|
request_header = self.request_header()
|
|
1661
1829
|
retries += 1
|
|
1662
1830
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1728,21 +1896,23 @@ class M365(object):
|
|
|
1728
1896
|
|
|
1729
1897
|
request_header = self.request_header()
|
|
1730
1898
|
|
|
1731
|
-
logger.
|
|
1732
|
-
"Lookup Microsoft 365 Teams with name -> %s; calling -> %s",
|
|
1899
|
+
logger.debug(
|
|
1900
|
+
"Lookup Microsoft 365 Teams with name -> '%s'; calling -> %s",
|
|
1733
1901
|
name,
|
|
1734
1902
|
request_url,
|
|
1735
1903
|
)
|
|
1736
1904
|
|
|
1737
1905
|
retries = 0
|
|
1738
1906
|
while True:
|
|
1739
|
-
response = requests.get(
|
|
1907
|
+
response = requests.get(
|
|
1908
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
1909
|
+
)
|
|
1740
1910
|
if response.ok:
|
|
1741
1911
|
return self.parse_request_response(response)
|
|
1742
1912
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1743
1913
|
elif response.status_code == 401 and retries == 0:
|
|
1744
|
-
logger.
|
|
1745
|
-
self.authenticate(True)
|
|
1914
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1915
|
+
self.authenticate(revalidate=True)
|
|
1746
1916
|
request_header = self.request_header()
|
|
1747
1917
|
retries += 1
|
|
1748
1918
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1779,7 +1949,7 @@ class M365(object):
|
|
|
1779
1949
|
group_id = self.get_result_value(response, "id", 0)
|
|
1780
1950
|
if not group_id:
|
|
1781
1951
|
logger.error(
|
|
1782
|
-
"M365 Group -> %s not found. It is required for creating a corresponding M365 Team.",
|
|
1952
|
+
"M365 Group -> '%s' not found. It is required for creating a corresponding M365 Team.",
|
|
1783
1953
|
name,
|
|
1784
1954
|
)
|
|
1785
1955
|
return None
|
|
@@ -1787,7 +1957,7 @@ class M365(object):
|
|
|
1787
1957
|
response = self.get_group_owners(name)
|
|
1788
1958
|
if response is None or not "value" in response or not response["value"]:
|
|
1789
1959
|
logger.warning(
|
|
1790
|
-
"M365 Group -> %s has no owners. This is required for creating a corresponding M365 Team.",
|
|
1960
|
+
"M365 Group -> '%s' has no owners. This is required for creating a corresponding M365 Team.",
|
|
1791
1961
|
name,
|
|
1792
1962
|
)
|
|
1793
1963
|
return None
|
|
@@ -1802,7 +1972,7 @@ class M365(object):
|
|
|
1802
1972
|
request_url = self.config()["teamsUrl"]
|
|
1803
1973
|
request_header = self.request_header()
|
|
1804
1974
|
|
|
1805
|
-
logger.
|
|
1975
|
+
logger.debug("Adding M365 Team -> %s; calling -> %s", name, request_url)
|
|
1806
1976
|
logger.debug("M365 Team attributes -> %s", team_post_body)
|
|
1807
1977
|
|
|
1808
1978
|
retries = 0
|
|
@@ -1811,14 +1981,14 @@ class M365(object):
|
|
|
1811
1981
|
request_url,
|
|
1812
1982
|
data=json.dumps(team_post_body),
|
|
1813
1983
|
headers=request_header,
|
|
1814
|
-
timeout=
|
|
1984
|
+
timeout=REQUEST_TIMEOUT,
|
|
1815
1985
|
)
|
|
1816
1986
|
if response.ok:
|
|
1817
1987
|
return self.parse_request_response(response)
|
|
1818
1988
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1819
1989
|
elif response.status_code == 401 and retries == 0:
|
|
1820
|
-
logger.
|
|
1821
|
-
self.authenticate(True)
|
|
1990
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
1991
|
+
self.authenticate(revalidate=True)
|
|
1822
1992
|
request_header = self.request_header()
|
|
1823
1993
|
retries += 1
|
|
1824
1994
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1831,7 +2001,7 @@ class M365(object):
|
|
|
1831
2001
|
retries += 1
|
|
1832
2002
|
else:
|
|
1833
2003
|
logger.error(
|
|
1834
|
-
"Failed to add M365 Team -> %s; status -> %s; error -> %s",
|
|
2004
|
+
"Failed to add M365 Team -> '%s'; status -> %s; error -> %s",
|
|
1835
2005
|
name,
|
|
1836
2006
|
response.status_code,
|
|
1837
2007
|
response.text,
|
|
@@ -1853,7 +2023,7 @@ class M365(object):
|
|
|
1853
2023
|
|
|
1854
2024
|
request_header = self.request_header()
|
|
1855
2025
|
|
|
1856
|
-
logger.
|
|
2026
|
+
logger.debug(
|
|
1857
2027
|
"Delete Microsoft 365 Teams with ID -> %s; calling -> %s",
|
|
1858
2028
|
team_id,
|
|
1859
2029
|
request_url,
|
|
@@ -1861,13 +2031,15 @@ class M365(object):
|
|
|
1861
2031
|
|
|
1862
2032
|
retries = 0
|
|
1863
2033
|
while True:
|
|
1864
|
-
response = requests.delete(
|
|
2034
|
+
response = requests.delete(
|
|
2035
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2036
|
+
)
|
|
1865
2037
|
if response.ok:
|
|
1866
2038
|
return self.parse_request_response(response)
|
|
1867
2039
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1868
2040
|
elif response.status_code == 401 and retries == 0:
|
|
1869
|
-
logger.
|
|
1870
|
-
self.authenticate(True)
|
|
2041
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2042
|
+
self.authenticate(revalidate=True)
|
|
1871
2043
|
request_header = self.request_header()
|
|
1872
2044
|
retries += 1
|
|
1873
2045
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1905,22 +2077,24 @@ class M365(object):
|
|
|
1905
2077
|
|
|
1906
2078
|
request_header = self.request_header()
|
|
1907
2079
|
|
|
1908
|
-
logger.
|
|
1909
|
-
"Delete all Microsoft 365 Teams with name -> %s; calling -> %s",
|
|
2080
|
+
logger.debug(
|
|
2081
|
+
"Delete all Microsoft 365 Teams with name -> '%s'; calling -> %s",
|
|
1910
2082
|
name,
|
|
1911
2083
|
request_url,
|
|
1912
2084
|
)
|
|
1913
2085
|
|
|
1914
2086
|
retries = 0
|
|
1915
2087
|
while True:
|
|
1916
|
-
response = requests.get(
|
|
2088
|
+
response = requests.get(
|
|
2089
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2090
|
+
)
|
|
1917
2091
|
if response.ok:
|
|
1918
2092
|
existing_teams = self.parse_request_response(response)
|
|
1919
2093
|
break
|
|
1920
2094
|
# Check if Session has expired - then re-authenticate and try once more
|
|
1921
2095
|
elif response.status_code == 401 and retries == 0:
|
|
1922
|
-
logger.
|
|
1923
|
-
self.authenticate(True)
|
|
2096
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2097
|
+
self.authenticate(revalidate=True)
|
|
1924
2098
|
request_header = self.request_header()
|
|
1925
2099
|
retries += 1
|
|
1926
2100
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -1956,16 +2130,16 @@ class M365(object):
|
|
|
1956
2130
|
counter += 1
|
|
1957
2131
|
|
|
1958
2132
|
logger.info(
|
|
1959
|
-
"%s M365 Teams with name -> %s have been deleted.",
|
|
2133
|
+
"%s M365 Teams with name -> '%s' have been deleted.",
|
|
1960
2134
|
str(counter),
|
|
1961
2135
|
name,
|
|
1962
2136
|
)
|
|
1963
2137
|
return True
|
|
1964
2138
|
else:
|
|
1965
|
-
logger.info("No M365 Teams with name -> %s found.", name)
|
|
2139
|
+
logger.info("No M365 Teams with name -> '%s' found.", name)
|
|
1966
2140
|
return False
|
|
1967
2141
|
else:
|
|
1968
|
-
logger.error("Failed to retrieve M365 Teams with name -> %s", name)
|
|
2142
|
+
logger.error("Failed to retrieve M365 Teams with name -> '%s'", name)
|
|
1969
2143
|
return False
|
|
1970
2144
|
|
|
1971
2145
|
# end method definition
|
|
@@ -1989,19 +2163,20 @@ class M365(object):
|
|
|
1989
2163
|
if not "value" in response or not response["value"]:
|
|
1990
2164
|
return False
|
|
1991
2165
|
groups = response["value"]
|
|
2166
|
+
|
|
1992
2167
|
logger.info(
|
|
1993
2168
|
"Found -> %s existing M365 groups. Checking which ones should be deleted...",
|
|
1994
2169
|
len(groups),
|
|
1995
2170
|
)
|
|
1996
2171
|
|
|
1997
|
-
# Process all groups and check if
|
|
2172
|
+
# Process all groups and check if they should be
|
|
1998
2173
|
# deleted:
|
|
1999
2174
|
for group in groups:
|
|
2000
2175
|
group_name = group["displayName"]
|
|
2001
2176
|
# Check if group is in exception list:
|
|
2002
2177
|
if group_name in exception_list:
|
|
2003
2178
|
logger.info(
|
|
2004
|
-
"M365 Group name -> %s is on the exception list. Skipping...",
|
|
2179
|
+
"M365 Group name -> '%s' is on the exception list. Skipping...",
|
|
2005
2180
|
group_name,
|
|
2006
2181
|
)
|
|
2007
2182
|
continue
|
|
@@ -2010,7 +2185,7 @@ class M365(object):
|
|
|
2010
2185
|
result = re.search(pattern, group_name)
|
|
2011
2186
|
if result:
|
|
2012
2187
|
logger.info(
|
|
2013
|
-
"M365 Group name -> %s is matching pattern -> %s. Delete it now...",
|
|
2188
|
+
"M365 Group name -> '%s' is matching pattern -> %s. Delete it now...",
|
|
2014
2189
|
group_name,
|
|
2015
2190
|
pattern,
|
|
2016
2191
|
)
|
|
@@ -2018,7 +2193,7 @@ class M365(object):
|
|
|
2018
2193
|
break
|
|
2019
2194
|
else:
|
|
2020
2195
|
logger.info(
|
|
2021
|
-
"M365 Group name -> %s is not matching any delete pattern. Skipping...",
|
|
2196
|
+
"M365 Group name -> '%s' is not matching any delete pattern. Skipping...",
|
|
2022
2197
|
group_name,
|
|
2023
2198
|
)
|
|
2024
2199
|
return True
|
|
@@ -2062,21 +2237,23 @@ class M365(object):
|
|
|
2062
2237
|
|
|
2063
2238
|
request_header = self.request_header()
|
|
2064
2239
|
|
|
2065
|
-
logger.
|
|
2066
|
-
"Retrieve channels of Microsoft 365 Team -> %s; calling -> %s",
|
|
2240
|
+
logger.debug(
|
|
2241
|
+
"Retrieve channels of Microsoft 365 Team -> '%s'; calling -> %s",
|
|
2067
2242
|
name,
|
|
2068
2243
|
request_url,
|
|
2069
2244
|
)
|
|
2070
2245
|
|
|
2071
2246
|
retries = 0
|
|
2072
2247
|
while True:
|
|
2073
|
-
response = requests.get(
|
|
2248
|
+
response = requests.get(
|
|
2249
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2250
|
+
)
|
|
2074
2251
|
if response.ok:
|
|
2075
2252
|
return self.parse_request_response(response)
|
|
2076
2253
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2077
2254
|
elif response.status_code == 401 and retries == 0:
|
|
2078
|
-
logger.
|
|
2079
|
-
self.authenticate(True)
|
|
2255
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2256
|
+
self.authenticate(revalidate=True)
|
|
2080
2257
|
request_header = self.request_header()
|
|
2081
2258
|
retries += 1
|
|
2082
2259
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2145,7 +2322,7 @@ class M365(object):
|
|
|
2145
2322
|
None,
|
|
2146
2323
|
)
|
|
2147
2324
|
if not channel:
|
|
2148
|
-
logger.
|
|
2325
|
+
logger.error(
|
|
2149
2326
|
"Cannot find Channel -> %s on M365 Team -> %s", channel_name, team_name
|
|
2150
2327
|
)
|
|
2151
2328
|
return None
|
|
@@ -2162,7 +2339,7 @@ class M365(object):
|
|
|
2162
2339
|
|
|
2163
2340
|
request_header = self.request_header()
|
|
2164
2341
|
|
|
2165
|
-
logger.
|
|
2342
|
+
logger.debug(
|
|
2166
2343
|
"Retrieve Tabs of Microsoft 365 Teams -> %s and Channel -> %s; calling -> %s",
|
|
2167
2344
|
team_name,
|
|
2168
2345
|
channel_name,
|
|
@@ -2171,13 +2348,15 @@ class M365(object):
|
|
|
2171
2348
|
|
|
2172
2349
|
retries = 0
|
|
2173
2350
|
while True:
|
|
2174
|
-
response = requests.get(
|
|
2351
|
+
response = requests.get(
|
|
2352
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2353
|
+
)
|
|
2175
2354
|
if response.ok:
|
|
2176
2355
|
return self.parse_request_response(response)
|
|
2177
2356
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2178
2357
|
elif response.status_code == 401 and retries == 0:
|
|
2179
|
-
logger.
|
|
2180
|
-
self.authenticate(True)
|
|
2358
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2359
|
+
self.authenticate(revalidate=True)
|
|
2181
2360
|
request_header = self.request_header()
|
|
2182
2361
|
retries += 1
|
|
2183
2362
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2251,25 +2430,27 @@ class M365(object):
|
|
|
2251
2430
|
request_url = self.config()["teamsAppsUrl"] + "?" + encoded_query
|
|
2252
2431
|
|
|
2253
2432
|
if filter_expression:
|
|
2254
|
-
logger.
|
|
2433
|
+
logger.debug(
|
|
2255
2434
|
"Get list of MS Teams Apps using filter -> %s; calling -> %s",
|
|
2256
2435
|
filter_expression,
|
|
2257
2436
|
request_url,
|
|
2258
2437
|
)
|
|
2259
2438
|
else:
|
|
2260
|
-
logger.
|
|
2439
|
+
logger.debug("Get list of all MS Teams Apps; calling -> %s", request_url)
|
|
2261
2440
|
|
|
2262
2441
|
request_header = self.request_header()
|
|
2263
2442
|
|
|
2264
2443
|
retries = 0
|
|
2265
2444
|
while True:
|
|
2266
|
-
response = requests.get(
|
|
2445
|
+
response = requests.get(
|
|
2446
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2447
|
+
)
|
|
2267
2448
|
if response.ok:
|
|
2268
2449
|
return self.parse_request_response(response)
|
|
2269
2450
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2270
2451
|
elif response.status_code == 401 and retries == 0:
|
|
2271
|
-
logger.
|
|
2272
|
-
self.authenticate(True)
|
|
2452
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2453
|
+
self.authenticate(revalidate=True)
|
|
2273
2454
|
request_header = self.request_header()
|
|
2274
2455
|
retries += 1
|
|
2275
2456
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2291,35 +2472,59 @@ class M365(object):
|
|
|
2291
2472
|
# end method definition
|
|
2292
2473
|
|
|
2293
2474
|
def get_teams_app(self, app_id: str) -> dict | None:
|
|
2294
|
-
"""Get a specific MS Teams app in catalog based on the known app ID
|
|
2475
|
+
"""Get a specific MS Teams app in catalog based on the known (internal) app ID
|
|
2295
2476
|
|
|
2296
2477
|
Args:
|
|
2297
|
-
app_id (str): ID of the app
|
|
2478
|
+
app_id (str): ID of the app (this is NOT the external ID but the internal ID)
|
|
2298
2479
|
Returns:
|
|
2299
2480
|
dict: response of the MS Graph API call or None if the call fails.
|
|
2481
|
+
|
|
2482
|
+
Examle response:
|
|
2483
|
+
{
|
|
2484
|
+
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#appCatalogs/teamsApps(appDefinitions())/$entity',
|
|
2485
|
+
'id': 'ccabe3fb-316f-40e0-a486-1659682cb8cd',
|
|
2486
|
+
'externalId': 'dd4af790-d8ff-47a0-87ad-486318272c7a',
|
|
2487
|
+
'displayName': 'Extended ECM',
|
|
2488
|
+
'distributionMethod': 'organization',
|
|
2489
|
+
'appDefinitions@odata.context': "https://graph.microsoft.com/v1.0/$metadata#appCatalogs/teamsApps('ccabe3fb-316f-40e0-a486-1659682cb8cd')/appDefinitions",
|
|
2490
|
+
'appDefinitions': [
|
|
2491
|
+
{
|
|
2492
|
+
'id': 'Y2NhYmUzZmItMzE2Zi00MGUwLWE0ODYtMTY1OTY4MmNiOGNkIyMyNC4yLjAjI1B1Ymxpc2hlZA==',
|
|
2493
|
+
'teamsAppId': 'ccabe3fb-316f-40e0-a486-1659682cb8cd',
|
|
2494
|
+
'displayName': 'Extended ECM',
|
|
2495
|
+
'version': '24.2.0',
|
|
2496
|
+
'publishingState': 'published',
|
|
2497
|
+
'shortDescription': 'Add a tab for an Extended ECM business workspace.',
|
|
2498
|
+
'description': 'View and interact with OpenText Extended ECM business workspaces',
|
|
2499
|
+
'lastModifiedDateTime': None,
|
|
2500
|
+
'createdBy': None,
|
|
2501
|
+
'authorization': {...}
|
|
2502
|
+
}
|
|
2503
|
+
]
|
|
2504
|
+
}
|
|
2300
2505
|
"""
|
|
2301
2506
|
|
|
2302
2507
|
query = {"$expand": "AppDefinitions"}
|
|
2303
2508
|
encoded_query = urllib.parse.urlencode(query, doseq=True)
|
|
2304
2509
|
request_url = self.config()["teamsAppsUrl"] + "/" + app_id + "?" + encoded_query
|
|
2305
2510
|
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
logger.info(
|
|
2309
|
-
"Get MS Teams App with ID -> %s; calling -> %s", app_id, request_url
|
|
2511
|
+
logger.debug(
|
|
2512
|
+
"Get M365 Teams App with ID -> %s; calling -> %s", app_id, request_url
|
|
2310
2513
|
)
|
|
2311
2514
|
|
|
2312
2515
|
request_header = self.request_header()
|
|
2313
2516
|
|
|
2314
2517
|
retries = 0
|
|
2315
2518
|
while True:
|
|
2316
|
-
response = requests.get(
|
|
2519
|
+
response = requests.get(
|
|
2520
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2521
|
+
)
|
|
2317
2522
|
if response.ok:
|
|
2318
2523
|
return self.parse_request_response(response)
|
|
2319
2524
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2320
2525
|
elif response.status_code == 401 and retries == 0:
|
|
2321
|
-
logger.
|
|
2322
|
-
self.authenticate(True)
|
|
2526
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2527
|
+
self.authenticate(revalidate=True)
|
|
2323
2528
|
request_header = self.request_header()
|
|
2324
2529
|
retries += 1
|
|
2325
2530
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2364,7 +2569,8 @@ class M365(object):
|
|
|
2364
2569
|
+ "/teamwork/installedApps?"
|
|
2365
2570
|
+ encoded_query
|
|
2366
2571
|
)
|
|
2367
|
-
|
|
2572
|
+
|
|
2573
|
+
logger.debug(
|
|
2368
2574
|
"Get list of M365 Teams Apps for user -> %s using query -> %s; calling -> %s",
|
|
2369
2575
|
user_id,
|
|
2370
2576
|
query,
|
|
@@ -2375,13 +2581,15 @@ class M365(object):
|
|
|
2375
2581
|
|
|
2376
2582
|
retries = 0
|
|
2377
2583
|
while True:
|
|
2378
|
-
response = requests.get(
|
|
2584
|
+
response = requests.get(
|
|
2585
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2586
|
+
)
|
|
2379
2587
|
if response.ok:
|
|
2380
2588
|
return self.parse_request_response(response)
|
|
2381
2589
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2382
2590
|
elif response.status_code == 401 and retries == 0:
|
|
2383
|
-
logger.
|
|
2384
|
-
self.authenticate(True)
|
|
2591
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2592
|
+
self.authenticate(revalidate=True)
|
|
2385
2593
|
request_header = self.request_header()
|
|
2386
2594
|
retries += 1
|
|
2387
2595
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2427,7 +2635,8 @@ class M365(object):
|
|
|
2427
2635
|
+ "/installedApps?"
|
|
2428
2636
|
+ encoded_query
|
|
2429
2637
|
)
|
|
2430
|
-
|
|
2638
|
+
|
|
2639
|
+
logger.debug(
|
|
2431
2640
|
"Get list of M365 Teams Apps for M365 Team -> %s using query -> %s; calling -> %s",
|
|
2432
2641
|
team_id,
|
|
2433
2642
|
query,
|
|
@@ -2438,13 +2647,15 @@ class M365(object):
|
|
|
2438
2647
|
|
|
2439
2648
|
retries = 0
|
|
2440
2649
|
while True:
|
|
2441
|
-
response = requests.get(
|
|
2650
|
+
response = requests.get(
|
|
2651
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2652
|
+
)
|
|
2442
2653
|
if response.ok:
|
|
2443
2654
|
return self.parse_request_response(response)
|
|
2444
2655
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2445
2656
|
elif response.status_code == 401 and retries == 0:
|
|
2446
|
-
logger.
|
|
2447
|
-
self.authenticate(True)
|
|
2657
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2658
|
+
self.authenticate(revalidate=True)
|
|
2448
2659
|
request_header = self.request_header()
|
|
2449
2660
|
retries += 1
|
|
2450
2661
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2495,6 +2706,7 @@ class M365(object):
|
|
|
2495
2706
|
but requires a token of a user authenticated with username + password.
|
|
2496
2707
|
See https://learn.microsoft.com/en-us/graph/api/teamsapp-publish
|
|
2497
2708
|
(permissions table on that page)
|
|
2709
|
+
For updates see: https://learn.microsoft.com/en-us/graph/api/teamsapp-update?view=graph-rest-1.0&tabs=http
|
|
2498
2710
|
|
|
2499
2711
|
Args:
|
|
2500
2712
|
app_path (str): file path (with directory) to the app package to upload
|
|
@@ -2505,6 +2717,34 @@ class M365(object):
|
|
|
2505
2717
|
after installation (which is tenant specific)
|
|
2506
2718
|
Returns:
|
|
2507
2719
|
dict: Response of the MS GRAPH API REST call or None if the request fails
|
|
2720
|
+
|
|
2721
|
+
The responses are different depending if it is an install or upgrade!!
|
|
2722
|
+
|
|
2723
|
+
Example return for upgrades ("teamsAppId" is the "internal" ID of the app):
|
|
2724
|
+
{
|
|
2725
|
+
'@odata.context': "https://graph.microsoft.com/v1.0/$metadata#appCatalogs/teamsApps('3f749cca-8cb0-4925-9fa0-ba7aca2014af')/appDefinitions/$entity",
|
|
2726
|
+
'id': 'M2Y3NDljY2EtOGNiMC00OTI1LTlmYTAtYmE3YWNhMjAxNGFmIyMyNC4yLjAjI1B1Ymxpc2hlZA==',
|
|
2727
|
+
'teamsAppId': '3f749cca-8cb0-4925-9fa0-ba7aca2014af',
|
|
2728
|
+
'displayName': 'IDEA-TE - Extended ECM 24.2.0',
|
|
2729
|
+
'version': '24.2.0',
|
|
2730
|
+
'publishingState': 'published',
|
|
2731
|
+
'shortDescription': 'Add a tab for an Extended ECM business workspace.',
|
|
2732
|
+
'description': 'View and interact with OpenText Extended ECM business workspaces',
|
|
2733
|
+
'lastModifiedDateTime': None,
|
|
2734
|
+
'createdBy': None,
|
|
2735
|
+
'authorization': {
|
|
2736
|
+
'requiredPermissionSet': {...}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
Example return for new installations ("id" is the "internal" ID of the app):
|
|
2741
|
+
{
|
|
2742
|
+
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#appCatalogs/teamsApps/$entity',
|
|
2743
|
+
'id': '6c672afd-37fc-46c6-8365-d499aba3808b',
|
|
2744
|
+
'externalId': 'dd4af790-d8ff-47a0-87ad-486318272c7a',
|
|
2745
|
+
'displayName': 'OpenText Extended ECM',
|
|
2746
|
+
'distributionMethod': 'organization'
|
|
2747
|
+
}
|
|
2508
2748
|
"""
|
|
2509
2749
|
|
|
2510
2750
|
if update_existing_app and not app_catalog_id:
|
|
@@ -2514,12 +2754,12 @@ class M365(object):
|
|
|
2514
2754
|
return None
|
|
2515
2755
|
|
|
2516
2756
|
if not os.path.exists(app_path):
|
|
2517
|
-
logger.error("M365 Teams app file ->
|
|
2757
|
+
logger.error("M365 Teams app file -> %s does not exist!", app_path)
|
|
2518
2758
|
return None
|
|
2519
2759
|
|
|
2520
2760
|
# Ensure that the app file is a zip file
|
|
2521
2761
|
if not app_path.endswith(".zip"):
|
|
2522
|
-
logger.error("M365 Teams app file ->
|
|
2762
|
+
logger.error("M365 Teams app file -> %s must be a zip file!", app_path)
|
|
2523
2763
|
return None
|
|
2524
2764
|
|
|
2525
2765
|
request_url = self.config()["teamsAppsUrl"]
|
|
@@ -2527,12 +2767,11 @@ class M365(object):
|
|
|
2527
2767
|
# the specific endpoint:
|
|
2528
2768
|
if update_existing_app:
|
|
2529
2769
|
request_url += "/" + app_catalog_id + "/appDefinitions"
|
|
2770
|
+
|
|
2530
2771
|
# Here we need the credentials of an authenticated user!
|
|
2531
2772
|
# (not the application credentials (client_id, client_secret))
|
|
2532
2773
|
request_header = self.request_header_user("application/zip")
|
|
2533
2774
|
|
|
2534
|
-
# upload_files = {'file': open(app_path, 'rb')}
|
|
2535
|
-
|
|
2536
2775
|
with open(app_path, "rb") as f:
|
|
2537
2776
|
app_data = f.read()
|
|
2538
2777
|
|
|
@@ -2540,12 +2779,13 @@ class M365(object):
|
|
|
2540
2779
|
# Ensure that the app file contains a manifest.json file
|
|
2541
2780
|
if "manifest.json" not in z.namelist():
|
|
2542
2781
|
logger.error(
|
|
2543
|
-
"M365 Teams app file ->
|
|
2782
|
+
"M365 Teams app file -> '%s' does not contain a manifest.json file!",
|
|
2783
|
+
app_path,
|
|
2544
2784
|
)
|
|
2545
2785
|
return None
|
|
2546
2786
|
|
|
2547
|
-
logger.
|
|
2548
|
-
"Upload M365 Teams app -> %s to the MS Teams catalog; calling -> %s",
|
|
2787
|
+
logger.debug(
|
|
2788
|
+
"Upload M365 Teams app -> '%s' to the MS Teams catalog; calling -> %s",
|
|
2549
2789
|
app_path,
|
|
2550
2790
|
request_url,
|
|
2551
2791
|
)
|
|
@@ -2553,15 +2793,18 @@ class M365(object):
|
|
|
2553
2793
|
retries = 0
|
|
2554
2794
|
while True:
|
|
2555
2795
|
response = requests.post(
|
|
2556
|
-
request_url,
|
|
2796
|
+
request_url,
|
|
2797
|
+
headers=request_header,
|
|
2798
|
+
data=app_data,
|
|
2799
|
+
timeout=REQUEST_TIMEOUT,
|
|
2557
2800
|
)
|
|
2558
2801
|
if response.ok:
|
|
2559
2802
|
return self.parse_request_response(response)
|
|
2560
2803
|
|
|
2561
2804
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2562
2805
|
if response.status_code == 401 and retries == 0:
|
|
2563
|
-
logger.
|
|
2564
|
-
self.authenticate(True)
|
|
2806
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2807
|
+
self.authenticate(revalidate=True)
|
|
2565
2808
|
request_header = self.request_header()
|
|
2566
2809
|
retries += 1
|
|
2567
2810
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2575,7 +2818,7 @@ class M365(object):
|
|
|
2575
2818
|
else:
|
|
2576
2819
|
if update_existing_app:
|
|
2577
2820
|
logger.warning(
|
|
2578
|
-
"Failed to update existing M365 Teams app -> %s (may be because it is not a new version); status -> %s; error -> %s",
|
|
2821
|
+
"Failed to update existing M365 Teams app -> '%s' (may be because it is not a new version); status -> %s; error -> %s",
|
|
2579
2822
|
app_path,
|
|
2580
2823
|
response.status_code,
|
|
2581
2824
|
response.text,
|
|
@@ -2583,7 +2826,7 @@ class M365(object):
|
|
|
2583
2826
|
|
|
2584
2827
|
else:
|
|
2585
2828
|
logger.error(
|
|
2586
|
-
"Failed to upload new M365 Teams app -> %s; status -> %s; error -> %s",
|
|
2829
|
+
"Failed to upload new M365 Teams app -> '%s'; status -> %s; error -> %s",
|
|
2587
2830
|
app_path,
|
|
2588
2831
|
response.status_code,
|
|
2589
2832
|
response.text,
|
|
@@ -2605,11 +2848,13 @@ class M365(object):
|
|
|
2605
2848
|
request_header = self.request_header_user()
|
|
2606
2849
|
|
|
2607
2850
|
# Make the DELETE request to remove the app from the app catalog
|
|
2608
|
-
response = requests.delete(
|
|
2851
|
+
response = requests.delete(
|
|
2852
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2853
|
+
)
|
|
2609
2854
|
|
|
2610
2855
|
# Check the status code of the response
|
|
2611
2856
|
if response.status_code == 204:
|
|
2612
|
-
logger.
|
|
2857
|
+
logger.debug(
|
|
2613
2858
|
"The M365 Teams app with ID -> %s has been successfully removed from the app catalog.",
|
|
2614
2859
|
app_id,
|
|
2615
2860
|
)
|
|
@@ -2622,35 +2867,160 @@ class M365(object):
|
|
|
2622
2867
|
|
|
2623
2868
|
# end method definition
|
|
2624
2869
|
|
|
2625
|
-
def assign_teams_app_to_user(
|
|
2870
|
+
def assign_teams_app_to_user(
|
|
2871
|
+
self,
|
|
2872
|
+
user_id: str,
|
|
2873
|
+
app_name: str = "",
|
|
2874
|
+
app_internal_id: str = "",
|
|
2875
|
+
show_error: bool = False,
|
|
2876
|
+
) -> dict | None:
|
|
2626
2877
|
"""Assigns (adds) a M365 Teams app to a M365 user.
|
|
2627
2878
|
|
|
2879
|
+
See: https://learn.microsoft.com/en-us/graph/api/userteamwork-post-installedapps?view=graph-rest-1.0&tabs=http
|
|
2880
|
+
|
|
2628
2881
|
Args:
|
|
2629
2882
|
user_id (str): M365 GUID of the user (can also be the M365 email of the user)
|
|
2630
|
-
app_name (str): exact name of the app
|
|
2883
|
+
app_name (str, optional): exact name of the app. Not needed if app_internal_id is provided
|
|
2884
|
+
app_internal_id (str, optional): internal ID of the app. If not provided it will be derived from app_name
|
|
2885
|
+
show_error (bool): whether or not an error should be displayed if the
|
|
2886
|
+
user is not found.
|
|
2631
2887
|
Returns:
|
|
2632
2888
|
dict: response of the MS Graph API call or None if the call fails.
|
|
2633
2889
|
"""
|
|
2634
2890
|
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2891
|
+
if not app_internal_id and not app_name:
|
|
2892
|
+
logger.error(
|
|
2893
|
+
"Either the internal App ID or the App name need to be provided!"
|
|
2894
|
+
)
|
|
2639
2895
|
return None
|
|
2640
2896
|
|
|
2897
|
+
if not app_internal_id:
|
|
2898
|
+
response = self.get_teams_apps(
|
|
2899
|
+
filter_expression="contains(displayName, '{}')".format(app_name)
|
|
2900
|
+
)
|
|
2901
|
+
app_internal_id = self.get_result_value(
|
|
2902
|
+
response=response, key="id", index=0
|
|
2903
|
+
)
|
|
2904
|
+
if not app_internal_id:
|
|
2905
|
+
logger.error(
|
|
2906
|
+
"M365 Teams App -> '%s' not found! Cannot assign App to user -> %s.",
|
|
2907
|
+
app_name,
|
|
2908
|
+
user_id,
|
|
2909
|
+
)
|
|
2910
|
+
return None
|
|
2911
|
+
|
|
2641
2912
|
request_url = (
|
|
2642
2913
|
self.config()["usersUrl"] + "/" + user_id + "/teamwork/installedApps"
|
|
2643
2914
|
)
|
|
2644
2915
|
request_header = self.request_header()
|
|
2645
2916
|
|
|
2646
2917
|
post_body = {
|
|
2647
|
-
"teamsApp@odata.bind": self.config()["teamsAppsUrl"] + "/" +
|
|
2918
|
+
"teamsApp@odata.bind": self.config()["teamsAppsUrl"] + "/" + app_internal_id
|
|
2648
2919
|
}
|
|
2649
2920
|
|
|
2650
|
-
logger.
|
|
2651
|
-
"Assign M365 Teams app -> %s (%s) to M365 user -> %s; calling -> %s",
|
|
2921
|
+
logger.debug(
|
|
2922
|
+
"Assign M365 Teams app -> '%s' (%s) to M365 user -> %s; calling -> %s",
|
|
2652
2923
|
app_name,
|
|
2653
|
-
|
|
2924
|
+
app_internal_id,
|
|
2925
|
+
user_id,
|
|
2926
|
+
request_url,
|
|
2927
|
+
)
|
|
2928
|
+
|
|
2929
|
+
retries = 0
|
|
2930
|
+
while True:
|
|
2931
|
+
response = requests.post(
|
|
2932
|
+
request_url,
|
|
2933
|
+
json=post_body,
|
|
2934
|
+
headers=request_header,
|
|
2935
|
+
timeout=REQUEST_TIMEOUT,
|
|
2936
|
+
)
|
|
2937
|
+
if response.ok:
|
|
2938
|
+
return self.parse_request_response(response)
|
|
2939
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
2940
|
+
elif response.status_code == 401 and retries == 0:
|
|
2941
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
2942
|
+
self.authenticate(revalidate=True)
|
|
2943
|
+
request_header = self.request_header()
|
|
2944
|
+
retries += 1
|
|
2945
|
+
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
2946
|
+
logger.warning(
|
|
2947
|
+
"M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
|
|
2948
|
+
response.status_code,
|
|
2949
|
+
(retries + 1) * 60,
|
|
2950
|
+
)
|
|
2951
|
+
time.sleep((retries + 1) * 60)
|
|
2952
|
+
retries += 1
|
|
2953
|
+
else:
|
|
2954
|
+
if show_error:
|
|
2955
|
+
logger.error(
|
|
2956
|
+
"Failed to assign M365 Teams app -> '%s' (%s) to M365 user -> %s; status -> %s; error -> %s",
|
|
2957
|
+
app_name,
|
|
2958
|
+
app_internal_id,
|
|
2959
|
+
user_id,
|
|
2960
|
+
response.status_code,
|
|
2961
|
+
response.text,
|
|
2962
|
+
)
|
|
2963
|
+
else:
|
|
2964
|
+
logger.warning(
|
|
2965
|
+
"Failed to assign M365 Teams app -> '%s' (%s) to M365 user -> %s (could be because the app is assigned organization-wide); status -> %s; warning -> %s",
|
|
2966
|
+
app_name,
|
|
2967
|
+
app_internal_id,
|
|
2968
|
+
user_id,
|
|
2969
|
+
response.status_code,
|
|
2970
|
+
response.text,
|
|
2971
|
+
)
|
|
2972
|
+
return None
|
|
2973
|
+
|
|
2974
|
+
# end method definition
|
|
2975
|
+
|
|
2976
|
+
def upgrade_teams_app_of_user(
|
|
2977
|
+
self, user_id: str, app_name: str, app_installation_id: str | None = None
|
|
2978
|
+
) -> dict | None:
|
|
2979
|
+
"""Upgrade a MS teams app for a user. The call will fail if the user does not
|
|
2980
|
+
already have the app assigned. So this needs to be checked before
|
|
2981
|
+
calling this method.
|
|
2982
|
+
See: https://learn.microsoft.com/en-us/graph/api/userteamwork-teamsappinstallation-upgrade?view=graph-rest-1.0&tabs=http
|
|
2983
|
+
|
|
2984
|
+
Args:
|
|
2985
|
+
user_id (str): M365 GUID of the user (can also be the M365 email of the user)
|
|
2986
|
+
app_name (str): exact name of the app
|
|
2987
|
+
app_installation_id (str): ID of the app installation for the user. This is neither the internal nor
|
|
2988
|
+
external app ID. It is specific for each user and app.
|
|
2989
|
+
Returns:
|
|
2990
|
+
dict: response of the MS Graph API call or None if the call fails.
|
|
2991
|
+
"""
|
|
2992
|
+
|
|
2993
|
+
if not app_installation_id:
|
|
2994
|
+
response = self.get_teams_apps_of_user(
|
|
2995
|
+
user_id=user_id,
|
|
2996
|
+
filter_expression="contains(teamsAppDefinition/displayName, '{}')".format(
|
|
2997
|
+
app_name
|
|
2998
|
+
),
|
|
2999
|
+
)
|
|
3000
|
+
# Retrieve the installation specific App ID - this is different from thew App catalalog ID!!
|
|
3001
|
+
app_installation_id = self.get_result_value(response, "id", 0)
|
|
3002
|
+
if not app_installation_id:
|
|
3003
|
+
logger.error(
|
|
3004
|
+
"M365 Teams app -> '%s' not found for user with ID -> %s. Cannot upgrade app for this user!",
|
|
3005
|
+
app_name,
|
|
3006
|
+
user_id,
|
|
3007
|
+
)
|
|
3008
|
+
return None
|
|
3009
|
+
|
|
3010
|
+
request_url = (
|
|
3011
|
+
self.config()["usersUrl"]
|
|
3012
|
+
+ "/"
|
|
3013
|
+
+ user_id
|
|
3014
|
+
+ "/teamwork/installedApps/"
|
|
3015
|
+
+ app_installation_id
|
|
3016
|
+
+ "/upgrade"
|
|
3017
|
+
)
|
|
3018
|
+
request_header = self.request_header()
|
|
3019
|
+
|
|
3020
|
+
logger.debug(
|
|
3021
|
+
"Upgrade M365 Teams app -> '%s' (%s) of M365 user with ID -> %s; calling -> %s",
|
|
3022
|
+
app_name,
|
|
3023
|
+
app_installation_id,
|
|
2654
3024
|
user_id,
|
|
2655
3025
|
request_url,
|
|
2656
3026
|
)
|
|
@@ -2658,14 +3028,14 @@ class M365(object):
|
|
|
2658
3028
|
retries = 0
|
|
2659
3029
|
while True:
|
|
2660
3030
|
response = requests.post(
|
|
2661
|
-
request_url,
|
|
3031
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
2662
3032
|
)
|
|
2663
3033
|
if response.ok:
|
|
2664
3034
|
return self.parse_request_response(response)
|
|
2665
3035
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2666
3036
|
elif response.status_code == 401 and retries == 0:
|
|
2667
|
-
logger.
|
|
2668
|
-
self.authenticate(True)
|
|
3037
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3038
|
+
self.authenticate(revalidate=True)
|
|
2669
3039
|
request_header = self.request_header()
|
|
2670
3040
|
retries += 1
|
|
2671
3041
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2678,9 +3048,9 @@ class M365(object):
|
|
|
2678
3048
|
retries += 1
|
|
2679
3049
|
else:
|
|
2680
3050
|
logger.error(
|
|
2681
|
-
"Failed to
|
|
3051
|
+
"Failed to upgrade M365 Teams app -> '%s' (%s) of M365 user -> %s; status -> %s; error -> %s",
|
|
2682
3052
|
app_name,
|
|
2683
|
-
|
|
3053
|
+
app_installation_id,
|
|
2684
3054
|
user_id,
|
|
2685
3055
|
response.status_code,
|
|
2686
3056
|
response.text,
|
|
@@ -2689,10 +3059,12 @@ class M365(object):
|
|
|
2689
3059
|
|
|
2690
3060
|
# end method definition
|
|
2691
3061
|
|
|
2692
|
-
def
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
3062
|
+
def remove_teams_app_from_user(
|
|
3063
|
+
self, user_id: str, app_name: str, app_installation_id: str | None = None
|
|
3064
|
+
) -> dict | None:
|
|
3065
|
+
"""Remove a M365 Teams app from a M365 user.
|
|
3066
|
+
|
|
3067
|
+
See: https://learn.microsoft.com/en-us/graph/api/userteamwork-delete-installedapps?view=graph-rest-1.0&tabs=http
|
|
2696
3068
|
|
|
2697
3069
|
Args:
|
|
2698
3070
|
user_id (str): M365 GUID of the user (can also be the M365 email of the user)
|
|
@@ -2701,14 +3073,18 @@ class M365(object):
|
|
|
2701
3073
|
dict: response of the MS Graph API call or None if the call fails.
|
|
2702
3074
|
"""
|
|
2703
3075
|
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
3076
|
+
if not app_installation_id:
|
|
3077
|
+
response = self.get_teams_apps_of_user(
|
|
3078
|
+
user_id=user_id,
|
|
3079
|
+
filter_expression="contains(teamsAppDefinition/displayName, '{}')".format(
|
|
3080
|
+
app_name
|
|
3081
|
+
),
|
|
3082
|
+
)
|
|
3083
|
+
# Retrieve the installation specific App ID - this is different from thew App catalalog ID!!
|
|
3084
|
+
app_installation_id = self.get_result_value(response, "id", 0)
|
|
2709
3085
|
if not app_installation_id:
|
|
2710
3086
|
logger.error(
|
|
2711
|
-
"M365 Teams app -> %s not found for user with ID -> %s. Cannot
|
|
3087
|
+
"M365 Teams app -> '%s' not found for user with ID -> %s. Cannot remove app from this user!",
|
|
2712
3088
|
app_name,
|
|
2713
3089
|
user_id,
|
|
2714
3090
|
)
|
|
@@ -2720,12 +3096,11 @@ class M365(object):
|
|
|
2720
3096
|
+ user_id
|
|
2721
3097
|
+ "/teamwork/installedApps/"
|
|
2722
3098
|
+ app_installation_id
|
|
2723
|
-
+ "/upgrade"
|
|
2724
3099
|
)
|
|
2725
3100
|
request_header = self.request_header()
|
|
2726
3101
|
|
|
2727
|
-
logger.
|
|
2728
|
-
"
|
|
3102
|
+
logger.debug(
|
|
3103
|
+
"Remove M365 Teams app -> '%s' (%s) from M365 user with ID -> %s; calling -> %s",
|
|
2729
3104
|
app_name,
|
|
2730
3105
|
app_installation_id,
|
|
2731
3106
|
user_id,
|
|
@@ -2734,13 +3109,15 @@ class M365(object):
|
|
|
2734
3109
|
|
|
2735
3110
|
retries = 0
|
|
2736
3111
|
while True:
|
|
2737
|
-
response = requests.
|
|
3112
|
+
response = requests.delete(
|
|
3113
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
3114
|
+
)
|
|
2738
3115
|
if response.ok:
|
|
2739
3116
|
return self.parse_request_response(response)
|
|
2740
3117
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2741
3118
|
elif response.status_code == 401 and retries == 0:
|
|
2742
|
-
logger.
|
|
2743
|
-
self.authenticate(True)
|
|
3119
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3120
|
+
self.authenticate(revalidate=True)
|
|
2744
3121
|
request_header = self.request_header()
|
|
2745
3122
|
retries += 1
|
|
2746
3123
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2753,7 +3130,7 @@ class M365(object):
|
|
|
2753
3130
|
retries += 1
|
|
2754
3131
|
else:
|
|
2755
3132
|
logger.error(
|
|
2756
|
-
"Failed to
|
|
3133
|
+
"Failed to remove M365 Teams app -> '%s' (%s) from M365 user -> %s; status -> %s; error -> %s",
|
|
2757
3134
|
app_name,
|
|
2758
3135
|
app_installation_id,
|
|
2759
3136
|
user_id,
|
|
@@ -2783,8 +3160,9 @@ class M365(object):
|
|
|
2783
3160
|
"teamsApp@odata.bind": self.config()["teamsAppsUrl"] + "/" + app_id
|
|
2784
3161
|
}
|
|
2785
3162
|
|
|
2786
|
-
logger.
|
|
2787
|
-
"Assign M365 Teams app -> %s to M365 Team -> %s; calling -> %s",
|
|
3163
|
+
logger.debug(
|
|
3164
|
+
"Assign M365 Teams app -> '%s' (%s) to M365 Team -> %s; calling -> %s",
|
|
3165
|
+
self.config()["teamsAppName"],
|
|
2788
3166
|
app_id,
|
|
2789
3167
|
team_id,
|
|
2790
3168
|
request_url,
|
|
@@ -2793,14 +3171,17 @@ class M365(object):
|
|
|
2793
3171
|
retries = 0
|
|
2794
3172
|
while True:
|
|
2795
3173
|
response = requests.post(
|
|
2796
|
-
request_url,
|
|
3174
|
+
request_url,
|
|
3175
|
+
json=post_body,
|
|
3176
|
+
headers=request_header,
|
|
3177
|
+
timeout=REQUEST_TIMEOUT,
|
|
2797
3178
|
)
|
|
2798
3179
|
if response.ok:
|
|
2799
3180
|
return self.parse_request_response(response)
|
|
2800
3181
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2801
3182
|
elif response.status_code == 401 and retries == 0:
|
|
2802
|
-
logger.
|
|
2803
|
-
self.authenticate(True)
|
|
3183
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3184
|
+
self.authenticate(revalidate=True)
|
|
2804
3185
|
request_header = self.request_header()
|
|
2805
3186
|
retries += 1
|
|
2806
3187
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2813,7 +3194,8 @@ class M365(object):
|
|
|
2813
3194
|
retries += 1
|
|
2814
3195
|
else:
|
|
2815
3196
|
logger.error(
|
|
2816
|
-
"Failed to assign M365 Teams app -> %s to M365 Team -> %s; status -> %s; error -> %s",
|
|
3197
|
+
"Failed to assign M365 Teams app -> '%s' (%s) to M365 Team -> %s; status -> %s; error -> %s",
|
|
3198
|
+
self.config()["teamsAppName"],
|
|
2817
3199
|
app_id,
|
|
2818
3200
|
team_id,
|
|
2819
3201
|
response.status_code,
|
|
@@ -2843,7 +3225,7 @@ class M365(object):
|
|
|
2843
3225
|
app_installation_id = self.get_result_value(response, "id", 0)
|
|
2844
3226
|
if not app_installation_id:
|
|
2845
3227
|
logger.error(
|
|
2846
|
-
"M365 Teams app -> %s not found for M365 Team with ID -> %s. Cannot upgrade app for this team!",
|
|
3228
|
+
"M365 Teams app -> '%s' not found for M365 Team with ID -> %s. Cannot upgrade app for this team!",
|
|
2847
3229
|
app_name,
|
|
2848
3230
|
team_id,
|
|
2849
3231
|
)
|
|
@@ -2859,8 +3241,8 @@ class M365(object):
|
|
|
2859
3241
|
)
|
|
2860
3242
|
request_header = self.request_header()
|
|
2861
3243
|
|
|
2862
|
-
logger.
|
|
2863
|
-
"Upgrade app -> %s (%s) of M365 team with ID -> %s; calling -> %s",
|
|
3244
|
+
logger.debug(
|
|
3245
|
+
"Upgrade app -> '%s' (%s) of M365 team with ID -> %s; calling -> %s",
|
|
2864
3246
|
app_name,
|
|
2865
3247
|
app_installation_id,
|
|
2866
3248
|
team_id,
|
|
@@ -2869,13 +3251,15 @@ class M365(object):
|
|
|
2869
3251
|
|
|
2870
3252
|
retries = 0
|
|
2871
3253
|
while True:
|
|
2872
|
-
response = requests.post(
|
|
3254
|
+
response = requests.post(
|
|
3255
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
3256
|
+
)
|
|
2873
3257
|
if response.ok:
|
|
2874
3258
|
return self.parse_request_response(response)
|
|
2875
3259
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2876
3260
|
elif response.status_code == 401 and retries == 0:
|
|
2877
|
-
logger.
|
|
2878
|
-
self.authenticate(True)
|
|
3261
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3262
|
+
self.authenticate(revalidate=True)
|
|
2879
3263
|
request_header = self.request_header()
|
|
2880
3264
|
retries += 1
|
|
2881
3265
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -2888,7 +3272,7 @@ class M365(object):
|
|
|
2888
3272
|
retries += 1
|
|
2889
3273
|
else:
|
|
2890
3274
|
logger.error(
|
|
2891
|
-
"Failed to upgrade app -> %s (%s) of M365 team with ID -> %s; status -> %s; error -> %s",
|
|
3275
|
+
"Failed to upgrade M365 Teams app -> '%s' (%s) of M365 team with ID -> %s; status -> %s; error -> %s",
|
|
2892
3276
|
app_name,
|
|
2893
3277
|
app_installation_id,
|
|
2894
3278
|
team_id,
|
|
@@ -2940,8 +3324,10 @@ class M365(object):
|
|
|
2940
3324
|
None,
|
|
2941
3325
|
)
|
|
2942
3326
|
if not channel:
|
|
2943
|
-
logger.
|
|
2944
|
-
"Cannot find Channel -> %s on M365 Team -> %s",
|
|
3327
|
+
logger.error(
|
|
3328
|
+
"Cannot find Channel -> '%s' on M365 Team -> '%s'",
|
|
3329
|
+
channel_name,
|
|
3330
|
+
team_name,
|
|
2945
3331
|
)
|
|
2946
3332
|
return None
|
|
2947
3333
|
channel_id = channel["id"]
|
|
@@ -2969,8 +3355,8 @@ class M365(object):
|
|
|
2969
3355
|
},
|
|
2970
3356
|
}
|
|
2971
3357
|
|
|
2972
|
-
logger.
|
|
2973
|
-
"Add Tab -> %s with App ID -> %s to Channel -> %s of Microsoft 365 Team -> %s; calling -> %s",
|
|
3358
|
+
logger.debug(
|
|
3359
|
+
"Add Tab -> '%s' with App ID -> %s to Channel -> '%s' of Microsoft 365 Team -> '%s'; calling -> %s",
|
|
2974
3360
|
tab_name,
|
|
2975
3361
|
app_id,
|
|
2976
3362
|
channel_name,
|
|
@@ -2981,14 +3367,17 @@ class M365(object):
|
|
|
2981
3367
|
retries = 0
|
|
2982
3368
|
while True:
|
|
2983
3369
|
response = requests.post(
|
|
2984
|
-
request_url,
|
|
3370
|
+
request_url,
|
|
3371
|
+
headers=request_header,
|
|
3372
|
+
json=tab_config,
|
|
3373
|
+
timeout=REQUEST_TIMEOUT,
|
|
2985
3374
|
)
|
|
2986
3375
|
if response.ok:
|
|
2987
3376
|
return self.parse_request_response(response)
|
|
2988
3377
|
# Check if Session has expired - then re-authenticate and try once more
|
|
2989
3378
|
elif response.status_code == 401 and retries == 0:
|
|
2990
|
-
logger.
|
|
2991
|
-
self.authenticate(True)
|
|
3379
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3380
|
+
self.authenticate(revalidate=True)
|
|
2992
3381
|
request_header = self.request_header()
|
|
2993
3382
|
retries += 1
|
|
2994
3383
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -3001,7 +3390,7 @@ class M365(object):
|
|
|
3001
3390
|
retries += 1
|
|
3002
3391
|
else:
|
|
3003
3392
|
logger.error(
|
|
3004
|
-
"Failed to add Tab for M365 Team -> %s (%s) and Channel -> %s (%s); status -> %s; error -> %s; tab config -> %s",
|
|
3393
|
+
"Failed to add Tab for M365 Team -> '%s' (%s) and Channel -> '%s' (%s); status -> %s; error -> %s; tab config -> %s",
|
|
3005
3394
|
team_name,
|
|
3006
3395
|
team_id,
|
|
3007
3396
|
channel_name,
|
|
@@ -3053,8 +3442,10 @@ class M365(object):
|
|
|
3053
3442
|
None,
|
|
3054
3443
|
)
|
|
3055
3444
|
if not channel:
|
|
3056
|
-
logger.
|
|
3057
|
-
"Cannot find Channel -> %s for M365 Team -> %s",
|
|
3445
|
+
logger.error(
|
|
3446
|
+
"Cannot find Channel -> '%s' for M365 Team -> '%s'",
|
|
3447
|
+
channel_name,
|
|
3448
|
+
team_name,
|
|
3058
3449
|
)
|
|
3059
3450
|
return None
|
|
3060
3451
|
channel_id = channel["id"]
|
|
@@ -3070,8 +3461,8 @@ class M365(object):
|
|
|
3070
3461
|
None,
|
|
3071
3462
|
)
|
|
3072
3463
|
if not tab:
|
|
3073
|
-
logger.
|
|
3074
|
-
"Cannot find Tab -> %s on M365 Team -> %s (%s) and Channel -> %s (%s)",
|
|
3464
|
+
logger.error(
|
|
3465
|
+
"Cannot find Tab -> '%s' on M365 Team -> '%s' (%s) and Channel -> '%s' (%s)",
|
|
3075
3466
|
tab_name,
|
|
3076
3467
|
team_name,
|
|
3077
3468
|
team_id,
|
|
@@ -3103,8 +3494,8 @@ class M365(object):
|
|
|
3103
3494
|
},
|
|
3104
3495
|
}
|
|
3105
3496
|
|
|
3106
|
-
logger.
|
|
3107
|
-
"Update Tab -> %s (%s) of Channel -> %s (%s) for Microsoft 365 Teams -> %s (%s) with configuration -> %s; calling -> %s",
|
|
3497
|
+
logger.debug(
|
|
3498
|
+
"Update Tab -> '%s' (%s) of Channel -> '%s' (%s) for Microsoft 365 Teams -> '%s' (%s) with configuration -> %s; calling -> %s",
|
|
3108
3499
|
tab_name,
|
|
3109
3500
|
tab_id,
|
|
3110
3501
|
channel_name,
|
|
@@ -3118,14 +3509,17 @@ class M365(object):
|
|
|
3118
3509
|
retries = 0
|
|
3119
3510
|
while True:
|
|
3120
3511
|
response = requests.patch(
|
|
3121
|
-
request_url,
|
|
3512
|
+
request_url,
|
|
3513
|
+
headers=request_header,
|
|
3514
|
+
json=tab_config,
|
|
3515
|
+
timeout=REQUEST_TIMEOUT,
|
|
3122
3516
|
)
|
|
3123
3517
|
if response.ok:
|
|
3124
3518
|
return self.parse_request_response(response)
|
|
3125
3519
|
# Check if Session has expired - then re-authenticate and try once more
|
|
3126
3520
|
elif response.status_code == 401 and retries == 0:
|
|
3127
|
-
logger.
|
|
3128
|
-
self.authenticate(True)
|
|
3521
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3522
|
+
self.authenticate(revalidate=True)
|
|
3129
3523
|
request_header = self.request_header()
|
|
3130
3524
|
retries += 1
|
|
3131
3525
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -3138,7 +3532,7 @@ class M365(object):
|
|
|
3138
3532
|
retries += 1
|
|
3139
3533
|
else:
|
|
3140
3534
|
logger.error(
|
|
3141
|
-
"Failed to update Tab -> %s (%s) for M365 Team -> %s (%s) and Channel -> %s (%s); status -> %s; error -> %s",
|
|
3535
|
+
"Failed to update Tab -> '%s' (%s) for M365 Team -> '%s' (%s) and Channel -> '%s' (%s); status -> %s; error -> %s",
|
|
3142
3536
|
tab_name,
|
|
3143
3537
|
tab_id,
|
|
3144
3538
|
team_name,
|
|
@@ -3184,8 +3578,10 @@ class M365(object):
|
|
|
3184
3578
|
None,
|
|
3185
3579
|
)
|
|
3186
3580
|
if not channel:
|
|
3187
|
-
logger.
|
|
3188
|
-
"Cannot find Channel -> %s for M365 Team -> %s",
|
|
3581
|
+
logger.error(
|
|
3582
|
+
"Cannot find Channel -> '%s' for M365 Team -> '%s'",
|
|
3583
|
+
channel_name,
|
|
3584
|
+
team_name,
|
|
3189
3585
|
)
|
|
3190
3586
|
return False
|
|
3191
3587
|
channel_id = channel["id"]
|
|
@@ -3201,8 +3597,8 @@ class M365(object):
|
|
|
3201
3597
|
item for item in response["value"] if item["displayName"] == tab_name
|
|
3202
3598
|
]
|
|
3203
3599
|
if not tab_list:
|
|
3204
|
-
logger.
|
|
3205
|
-
"Cannot find
|
|
3600
|
+
logger.error(
|
|
3601
|
+
"Cannot find Tab -> '%s' on M365 Team -> '%s' (%s) and Channel -> '%s' (%s)",
|
|
3206
3602
|
tab_name,
|
|
3207
3603
|
team_name,
|
|
3208
3604
|
team_id,
|
|
@@ -3226,8 +3622,8 @@ class M365(object):
|
|
|
3226
3622
|
|
|
3227
3623
|
request_header = self.request_header()
|
|
3228
3624
|
|
|
3229
|
-
logger.
|
|
3230
|
-
"Delete Tab -> %s (%s) from Channel -> %s (%s) of Microsoft 365 Teams -> %s (%s); calling -> %s",
|
|
3625
|
+
logger.debug(
|
|
3626
|
+
"Delete Tab -> '%s' (%s) from Channel -> '%s' (%s) of Microsoft 365 Teams -> '%s' (%s); calling -> %s",
|
|
3231
3627
|
tab_name,
|
|
3232
3628
|
tab_id,
|
|
3233
3629
|
channel_name,
|
|
@@ -3240,11 +3636,11 @@ class M365(object):
|
|
|
3240
3636
|
retries = 0
|
|
3241
3637
|
while True:
|
|
3242
3638
|
response = requests.delete(
|
|
3243
|
-
request_url, headers=request_header, timeout=
|
|
3639
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
3244
3640
|
)
|
|
3245
3641
|
if response.ok:
|
|
3246
|
-
logger.
|
|
3247
|
-
"Tab -> %s (%s) has been deleted from Channel -> %s (%s) of Microsoft 365 Teams -> %s (%s)",
|
|
3642
|
+
logger.debug(
|
|
3643
|
+
"Tab -> '%s' (%s) has been deleted from Channel -> '%s' (%s) of Microsoft 365 Teams -> '%s' (%s)",
|
|
3248
3644
|
tab_name,
|
|
3249
3645
|
tab_id,
|
|
3250
3646
|
channel_name,
|
|
@@ -3255,8 +3651,8 @@ class M365(object):
|
|
|
3255
3651
|
break
|
|
3256
3652
|
# Check if Session has expired - then re-authenticate and try once more
|
|
3257
3653
|
elif response.status_code == 401 and retries == 0:
|
|
3258
|
-
logger.
|
|
3259
|
-
self.authenticate(True)
|
|
3654
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3655
|
+
self.authenticate(revalidate=True)
|
|
3260
3656
|
request_header = self.request_header()
|
|
3261
3657
|
retries += 1
|
|
3262
3658
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -3269,7 +3665,7 @@ class M365(object):
|
|
|
3269
3665
|
retries += 1
|
|
3270
3666
|
else:
|
|
3271
3667
|
logger.error(
|
|
3272
|
-
"Failed to delete Tab -> %s (%s) for M365 Team -> %s (%s) and Channel -> %s (%s); status -> %s; error -> %s",
|
|
3668
|
+
"Failed to delete Tab -> '%s' (%s) for M365 Team -> '%s' (%s) and Channel -> '%s' (%s); status -> %s; error -> %s",
|
|
3273
3669
|
tab_name,
|
|
3274
3670
|
tab_id,
|
|
3275
3671
|
team_name,
|
|
@@ -3329,22 +3725,25 @@ class M365(object):
|
|
|
3329
3725
|
request_url = self.config()["securityUrl"] + "/sensitivityLabels"
|
|
3330
3726
|
request_header = self.request_header()
|
|
3331
3727
|
|
|
3332
|
-
logger.
|
|
3333
|
-
"Create M365 sensitivity label -> %s; calling -> %s", name, request_url
|
|
3728
|
+
logger.debug(
|
|
3729
|
+
"Create M365 sensitivity label -> '%s'; calling -> %s", name, request_url
|
|
3334
3730
|
)
|
|
3335
3731
|
|
|
3336
3732
|
# Send the POST request to create the label
|
|
3337
3733
|
response = requests.post(
|
|
3338
|
-
request_url,
|
|
3734
|
+
request_url,
|
|
3735
|
+
headers=request_header,
|
|
3736
|
+
data=json.dumps(payload),
|
|
3737
|
+
timeout=REQUEST_TIMEOUT,
|
|
3339
3738
|
)
|
|
3340
3739
|
|
|
3341
3740
|
# Check the response status code
|
|
3342
3741
|
if response.status_code == 201:
|
|
3343
|
-
logger.
|
|
3742
|
+
logger.debug("Label -> '%s' has been created successfully!", name)
|
|
3344
3743
|
return response
|
|
3345
3744
|
else:
|
|
3346
3745
|
logger.error(
|
|
3347
|
-
"Failed to create the M365 label -> %s! Response status code -> %s",
|
|
3746
|
+
"Failed to create the M365 label -> '%s'! Response status code -> %s",
|
|
3348
3747
|
name,
|
|
3349
3748
|
response.status_code,
|
|
3350
3749
|
)
|
|
@@ -3372,8 +3771,8 @@ class M365(object):
|
|
|
3372
3771
|
)
|
|
3373
3772
|
request_header = self.request_header()
|
|
3374
3773
|
|
|
3375
|
-
logger.
|
|
3376
|
-
"Assign label -> %s to user -> %s; calling -> %s",
|
|
3774
|
+
logger.debug(
|
|
3775
|
+
"Assign label -> '%s' to user -> '%s'; calling -> %s",
|
|
3377
3776
|
label_name,
|
|
3378
3777
|
user_email,
|
|
3379
3778
|
request_url,
|
|
@@ -3382,14 +3781,14 @@ class M365(object):
|
|
|
3382
3781
|
retries = 0
|
|
3383
3782
|
while True:
|
|
3384
3783
|
response = requests.post(
|
|
3385
|
-
request_url, headers=request_header, json=body, timeout=
|
|
3784
|
+
request_url, headers=request_header, json=body, timeout=REQUEST_TIMEOUT
|
|
3386
3785
|
)
|
|
3387
3786
|
if response.ok:
|
|
3388
3787
|
return self.parse_request_response(response)
|
|
3389
3788
|
# Check if Session has expired - then re-authenticate and try once more
|
|
3390
3789
|
elif response.status_code == 401 and retries == 0:
|
|
3391
|
-
logger.
|
|
3392
|
-
self.authenticate(True)
|
|
3790
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3791
|
+
self.authenticate(revalidate=True)
|
|
3393
3792
|
request_header = self.request_header()
|
|
3394
3793
|
retries += 1
|
|
3395
3794
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -3402,7 +3801,7 @@ class M365(object):
|
|
|
3402
3801
|
retries += 1
|
|
3403
3802
|
else:
|
|
3404
3803
|
logger.error(
|
|
3405
|
-
"Failed to assign label -> %s to M365 user -> %s; status -> %s; error -> %s",
|
|
3804
|
+
"Failed to assign label -> '%s' to M365 user -> '%s'; status -> %s; error -> %s",
|
|
3406
3805
|
label_name,
|
|
3407
3806
|
user_email,
|
|
3408
3807
|
response.status_code,
|
|
@@ -3433,7 +3832,7 @@ class M365(object):
|
|
|
3433
3832
|
|
|
3434
3833
|
# request_header = self.request_header()
|
|
3435
3834
|
|
|
3436
|
-
logger.
|
|
3835
|
+
logger.debug("Install Outlook Add-in from '%s' (NOT IMPLEMENTED)", app_path)
|
|
3437
3836
|
|
|
3438
3837
|
response = None
|
|
3439
3838
|
|
|
@@ -3459,21 +3858,23 @@ class M365(object):
|
|
|
3459
3858
|
] + "?$filter=displayName eq '{}'".format(app_registration_name)
|
|
3460
3859
|
request_header = self.request_header()
|
|
3461
3860
|
|
|
3462
|
-
logger.
|
|
3463
|
-
"Get Azure App Registration -> %s; calling -> %s",
|
|
3861
|
+
logger.debug(
|
|
3862
|
+
"Get Azure App Registration -> '%s'; calling -> %s",
|
|
3464
3863
|
app_registration_name,
|
|
3465
3864
|
request_url,
|
|
3466
3865
|
)
|
|
3467
3866
|
|
|
3468
3867
|
retries = 0
|
|
3469
3868
|
while True:
|
|
3470
|
-
response = requests.get(
|
|
3869
|
+
response = requests.get(
|
|
3870
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
3871
|
+
)
|
|
3471
3872
|
if response.ok:
|
|
3472
3873
|
return self.parse_request_response(response)
|
|
3473
3874
|
# Check if Session has expired - then re-authenticate and try once more
|
|
3474
3875
|
elif response.status_code == 401 and retries == 0:
|
|
3475
|
-
logger.
|
|
3476
|
-
self.authenticate(True)
|
|
3876
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3877
|
+
self.authenticate(revalidate=True)
|
|
3477
3878
|
request_header = self.request_header()
|
|
3478
3879
|
retries += 1
|
|
3479
3880
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -3486,7 +3887,7 @@ class M365(object):
|
|
|
3486
3887
|
retries += 1
|
|
3487
3888
|
else:
|
|
3488
3889
|
logger.error(
|
|
3489
|
-
"Cannot find Azure App Registration -> %s; status -> %s; error -> %s",
|
|
3890
|
+
"Cannot find Azure App Registration -> '%s'; status -> %s; error -> %s",
|
|
3490
3891
|
app_registration_name,
|
|
3491
3892
|
response.status_code,
|
|
3492
3893
|
response.text,
|
|
@@ -3568,14 +3969,14 @@ class M365(object):
|
|
|
3568
3969
|
request_url,
|
|
3569
3970
|
headers=request_header,
|
|
3570
3971
|
json=app_registration_data,
|
|
3571
|
-
timeout=
|
|
3972
|
+
timeout=REQUEST_TIMEOUT,
|
|
3572
3973
|
)
|
|
3573
3974
|
if response.ok:
|
|
3574
3975
|
return self.parse_request_response(response)
|
|
3575
3976
|
# Check if Session has expired - then re-authenticate and try once more
|
|
3576
3977
|
elif response.status_code == 401 and retries == 0:
|
|
3577
|
-
logger.
|
|
3578
|
-
self.authenticate(True)
|
|
3978
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
3979
|
+
self.authenticate(revalidate=True)
|
|
3579
3980
|
request_header = self.request_header()
|
|
3580
3981
|
retries += 1
|
|
3581
3982
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -3588,7 +3989,7 @@ class M365(object):
|
|
|
3588
3989
|
retries += 1
|
|
3589
3990
|
else:
|
|
3590
3991
|
logger.error(
|
|
3591
|
-
"Cannot add App Registration -> %s; status -> %s; error -> %s",
|
|
3992
|
+
"Cannot add App Registration -> '%s'; status -> %s; error -> %s",
|
|
3592
3993
|
app_registration_name,
|
|
3593
3994
|
response.status_code,
|
|
3594
3995
|
response.text,
|
|
@@ -3627,8 +4028,8 @@ class M365(object):
|
|
|
3627
4028
|
request_url = self.config()["applicationsUrl"] + "/" + app_registration_id
|
|
3628
4029
|
request_header = self.request_header()
|
|
3629
4030
|
|
|
3630
|
-
logger.
|
|
3631
|
-
"Update App Registration -> %s (%s); calling -> %s",
|
|
4031
|
+
logger.debug(
|
|
4032
|
+
"Update App Registration -> '%s' (%s); calling -> %s",
|
|
3632
4033
|
app_registration_name,
|
|
3633
4034
|
app_registration_id,
|
|
3634
4035
|
request_url,
|
|
@@ -3640,14 +4041,14 @@ class M365(object):
|
|
|
3640
4041
|
request_url,
|
|
3641
4042
|
headers=request_header,
|
|
3642
4043
|
json=app_registration_data,
|
|
3643
|
-
timeout=
|
|
4044
|
+
timeout=REQUEST_TIMEOUT,
|
|
3644
4045
|
)
|
|
3645
4046
|
if response.ok:
|
|
3646
4047
|
return self.parse_request_response(response)
|
|
3647
4048
|
# Check if Session has expired - then re-authenticate and try once more
|
|
3648
4049
|
elif response.status_code == 401 and retries == 0:
|
|
3649
|
-
logger.
|
|
3650
|
-
self.authenticate(True)
|
|
4050
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
4051
|
+
self.authenticate(revalidate=True)
|
|
3651
4052
|
request_header = self.request_header()
|
|
3652
4053
|
retries += 1
|
|
3653
4054
|
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
@@ -3660,7 +4061,7 @@ class M365(object):
|
|
|
3660
4061
|
retries += 1
|
|
3661
4062
|
else:
|
|
3662
4063
|
logger.error(
|
|
3663
|
-
"Cannot update App Registration -> %s (%s); status -> %s; error -> %s",
|
|
4064
|
+
"Cannot update App Registration -> '%s' (%s); status -> %s; error -> %s",
|
|
3664
4065
|
app_registration_name,
|
|
3665
4066
|
app_registration_id,
|
|
3666
4067
|
response.status_code,
|
|
@@ -3669,3 +4070,522 @@ class M365(object):
|
|
|
3669
4070
|
return None
|
|
3670
4071
|
|
|
3671
4072
|
# end method definition
|
|
4073
|
+
|
|
4074
|
+
def get_mail(
|
|
4075
|
+
self,
|
|
4076
|
+
user_id: str,
|
|
4077
|
+
sender: str,
|
|
4078
|
+
subject: str,
|
|
4079
|
+
num_emails: int | None = None,
|
|
4080
|
+
show_error: bool = False,
|
|
4081
|
+
) -> dict | None:
|
|
4082
|
+
"""Get email from inbox of a given user and a given sender (from)
|
|
4083
|
+
This requires Mail.Read Application permissions for the Azure App being used.
|
|
4084
|
+
|
|
4085
|
+
Args:
|
|
4086
|
+
user_id (str): M365 ID of the user
|
|
4087
|
+
sender (str): sender email address to filter for
|
|
4088
|
+
num_emails (int, optional): number of matching emails to retrieve
|
|
4089
|
+
show_error (bool): whether or not an error should be displayed if the
|
|
4090
|
+
user is not found.
|
|
4091
|
+
Returns:
|
|
4092
|
+
dict: Email or None of the request fails.
|
|
4093
|
+
"""
|
|
4094
|
+
|
|
4095
|
+
# Attention: you can easily run in limitation of the MS Graph API. If selection + filtering
|
|
4096
|
+
# is too complex you can get this error: "The restriction or sort order is too complex for this operation."
|
|
4097
|
+
# that's why we first just do the ordering and then do the filtering on sender and subject
|
|
4098
|
+
# separately
|
|
4099
|
+
request_url = (
|
|
4100
|
+
self.config()["usersUrl"]
|
|
4101
|
+
+ "/"
|
|
4102
|
+
+ user_id
|
|
4103
|
+
# + "/messages?$filter=from/emailAddress/address eq '{}' and contains(subject, '{}')&$orderby=receivedDateTime desc".format(
|
|
4104
|
+
+ "/messages?$orderby=receivedDateTime desc"
|
|
4105
|
+
)
|
|
4106
|
+
if num_emails:
|
|
4107
|
+
request_url += "&$top={}".format(num_emails)
|
|
4108
|
+
|
|
4109
|
+
request_header = self.request_header()
|
|
4110
|
+
|
|
4111
|
+
logger.debug(
|
|
4112
|
+
"Retrieve mails for user -> %s from -> '%s' with subject -> '%s'; calling -> %s",
|
|
4113
|
+
user_id,
|
|
4114
|
+
sender,
|
|
4115
|
+
subject,
|
|
4116
|
+
request_url,
|
|
4117
|
+
)
|
|
4118
|
+
|
|
4119
|
+
retries = 0
|
|
4120
|
+
while True:
|
|
4121
|
+
response = requests.get(
|
|
4122
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
4123
|
+
)
|
|
4124
|
+
if response.ok:
|
|
4125
|
+
response = self.parse_request_response(response)
|
|
4126
|
+
messages = response["value"] if response else []
|
|
4127
|
+
|
|
4128
|
+
# Filter the messages by sender and subject in code
|
|
4129
|
+
filtered_messages = [
|
|
4130
|
+
msg
|
|
4131
|
+
for msg in messages
|
|
4132
|
+
if msg.get("from", {}).get("emailAddress", {}).get("address")
|
|
4133
|
+
== sender
|
|
4134
|
+
and subject in msg.get("subject", "")
|
|
4135
|
+
]
|
|
4136
|
+
response["value"] = filtered_messages
|
|
4137
|
+
return response
|
|
4138
|
+
|
|
4139
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
4140
|
+
elif response.status_code == 401 and retries == 0:
|
|
4141
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
4142
|
+
self.authenticate(revalidate=True)
|
|
4143
|
+
request_header = self.request_header()
|
|
4144
|
+
retries += 1
|
|
4145
|
+
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
4146
|
+
logger.warning(
|
|
4147
|
+
"M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
|
|
4148
|
+
response.status_code,
|
|
4149
|
+
(retries + 1) * 60,
|
|
4150
|
+
)
|
|
4151
|
+
time.sleep((retries + 1) * 60)
|
|
4152
|
+
retries += 1
|
|
4153
|
+
else:
|
|
4154
|
+
if show_error:
|
|
4155
|
+
logger.error(
|
|
4156
|
+
"Cannot retrieve emails for user -> %s; status -> %s; error -> %s",
|
|
4157
|
+
user_id,
|
|
4158
|
+
response.status_code,
|
|
4159
|
+
response.text,
|
|
4160
|
+
)
|
|
4161
|
+
else:
|
|
4162
|
+
logger.warning(
|
|
4163
|
+
"Cannot retrieve emails for user -> %s; status -> %s; warning -> %s",
|
|
4164
|
+
user_id,
|
|
4165
|
+
response.status_code,
|
|
4166
|
+
response.text,
|
|
4167
|
+
)
|
|
4168
|
+
return None
|
|
4169
|
+
|
|
4170
|
+
# end method definition
|
|
4171
|
+
|
|
4172
|
+
def get_mail_body(self, user_id: str, email_id: str) -> str:
|
|
4173
|
+
"""Get full email body for a given email ID
|
|
4174
|
+
This requires Mail.Read Application permissions for the Azure App being used.
|
|
4175
|
+
|
|
4176
|
+
Args:
|
|
4177
|
+
user_id (str): M365 ID of the user
|
|
4178
|
+
email_id (str): M365 ID of the email
|
|
4179
|
+
Returns:
|
|
4180
|
+
str: Email body or None of the request fails.
|
|
4181
|
+
"""
|
|
4182
|
+
|
|
4183
|
+
request_url = (
|
|
4184
|
+
self.config()["usersUrl"]
|
|
4185
|
+
+ "/"
|
|
4186
|
+
+ user_id
|
|
4187
|
+
+ "/messages/"
|
|
4188
|
+
+ email_id
|
|
4189
|
+
+ "/$value"
|
|
4190
|
+
)
|
|
4191
|
+
|
|
4192
|
+
request_header = self.request_header()
|
|
4193
|
+
|
|
4194
|
+
retries = 0
|
|
4195
|
+
while True:
|
|
4196
|
+
response = requests.get(
|
|
4197
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
4198
|
+
)
|
|
4199
|
+
if response.ok:
|
|
4200
|
+
return response.content.decode("utf-8")
|
|
4201
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
4202
|
+
elif response.status_code == 401 and retries == 0:
|
|
4203
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
4204
|
+
self.authenticate(revalidate=True)
|
|
4205
|
+
request_header = self.request_header()
|
|
4206
|
+
retries += 1
|
|
4207
|
+
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
4208
|
+
logger.warning(
|
|
4209
|
+
"M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
|
|
4210
|
+
response.status_code,
|
|
4211
|
+
(retries + 1) * 60,
|
|
4212
|
+
)
|
|
4213
|
+
time.sleep((retries + 1) * 60)
|
|
4214
|
+
retries += 1
|
|
4215
|
+
else:
|
|
4216
|
+
logger.error(
|
|
4217
|
+
"Cannot retrieve emails body for user -> %s and email -> %s; status -> %s; error -> %s",
|
|
4218
|
+
user_id,
|
|
4219
|
+
email_id,
|
|
4220
|
+
response.status_code,
|
|
4221
|
+
response.text,
|
|
4222
|
+
)
|
|
4223
|
+
return None
|
|
4224
|
+
|
|
4225
|
+
# end method definition
|
|
4226
|
+
|
|
4227
|
+
def extract_url_from_message_body(
|
|
4228
|
+
self,
|
|
4229
|
+
message_body: str,
|
|
4230
|
+
search_pattern: str,
|
|
4231
|
+
multi_line: bool = False,
|
|
4232
|
+
multi_line_end_marker: str = "%3D",
|
|
4233
|
+
line_end_marker: str = "=",
|
|
4234
|
+
replacements: list | None = None,
|
|
4235
|
+
) -> str | None:
|
|
4236
|
+
"""Parse the email body to extract (a potentially multi-line) URL from the body.
|
|
4237
|
+
|
|
4238
|
+
Args:
|
|
4239
|
+
message_body (str): Text of the Email body
|
|
4240
|
+
search_pattern (str): Pattern thatneeds to be in first line of the URL. This
|
|
4241
|
+
makes sure it is the right URL we are looking for.
|
|
4242
|
+
multi_line (bool, optional): Is the URL spread over multiple lines?. Defaults to False.
|
|
4243
|
+
multi_line_end_marker (str, optional): If it is a multi-line URL, what marks the end
|
|
4244
|
+
of the URL in the last line? Defaults to "%3D".
|
|
4245
|
+
line_end_marker (str, optional): What makrs the end of lines 1-(n-1)? Defaults to "=".
|
|
4246
|
+
Returns:
|
|
4247
|
+
str: URL text thathas been extracted.
|
|
4248
|
+
"""
|
|
4249
|
+
|
|
4250
|
+
if not message_body:
|
|
4251
|
+
return None
|
|
4252
|
+
|
|
4253
|
+
# Split all the lines after a CRLF:
|
|
4254
|
+
lines = [line.strip() for line in message_body.split("\r\n")]
|
|
4255
|
+
|
|
4256
|
+
# Filter out the complete URL from the extracted URLs
|
|
4257
|
+
found = False
|
|
4258
|
+
|
|
4259
|
+
url = ""
|
|
4260
|
+
|
|
4261
|
+
for line in lines:
|
|
4262
|
+
if found:
|
|
4263
|
+
# Remove line end marker - many times a "="
|
|
4264
|
+
if line.endswith(line_end_marker):
|
|
4265
|
+
line = line[:-1]
|
|
4266
|
+
for replacement in replacements:
|
|
4267
|
+
line = line.replace(replacement["from"], replacement["to"])
|
|
4268
|
+
# We consider an empty line after we found the URL to indicate the end of the URL:
|
|
4269
|
+
if line == "":
|
|
4270
|
+
break
|
|
4271
|
+
url += line
|
|
4272
|
+
if multi_line and line.endswith(multi_line_end_marker):
|
|
4273
|
+
break
|
|
4274
|
+
if not search_pattern in line:
|
|
4275
|
+
continue
|
|
4276
|
+
# Fine https:// in the current line:
|
|
4277
|
+
index = line.find("https://")
|
|
4278
|
+
if index == -1:
|
|
4279
|
+
continue
|
|
4280
|
+
# If there's any text in front of https in that line cut it:
|
|
4281
|
+
line = line[index:]
|
|
4282
|
+
# Remove line end marker - many times a "="
|
|
4283
|
+
if line.endswith(line_end_marker):
|
|
4284
|
+
line = line[:-1]
|
|
4285
|
+
for replacement in replacements:
|
|
4286
|
+
line = line.replace(replacement["from"], replacement["to"])
|
|
4287
|
+
found = True
|
|
4288
|
+
url += line
|
|
4289
|
+
if not multi_line:
|
|
4290
|
+
break
|
|
4291
|
+
|
|
4292
|
+
return url
|
|
4293
|
+
|
|
4294
|
+
# end method definition
|
|
4295
|
+
|
|
4296
|
+
def delete_mail(self, user_id: str, email_id: str) -> dict | None:
|
|
4297
|
+
"""Delete email from inbox of a given user and a given email ID.
|
|
4298
|
+
This requires Mail.ReadWrite Application permissions for the Azure App being used.
|
|
4299
|
+
|
|
4300
|
+
Args:
|
|
4301
|
+
user_id (str): M365 ID of the user
|
|
4302
|
+
email_id (str): M365 ID of the email
|
|
4303
|
+
Returns:
|
|
4304
|
+
dict: Email or None of the request fails.
|
|
4305
|
+
"""
|
|
4306
|
+
|
|
4307
|
+
request_url = (
|
|
4308
|
+
self.config()["usersUrl"] + "/" + user_id + "/messages/" + email_id
|
|
4309
|
+
)
|
|
4310
|
+
|
|
4311
|
+
request_header = self.request_header()
|
|
4312
|
+
|
|
4313
|
+
retries = 0
|
|
4314
|
+
while True:
|
|
4315
|
+
response = requests.delete(
|
|
4316
|
+
request_url, headers=request_header, timeout=REQUEST_TIMEOUT
|
|
4317
|
+
)
|
|
4318
|
+
if response.ok:
|
|
4319
|
+
return self.parse_request_response(response)
|
|
4320
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
4321
|
+
elif response.status_code == 401 and retries == 0:
|
|
4322
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
4323
|
+
self.authenticate(revalidate=True)
|
|
4324
|
+
request_header = self.request_header()
|
|
4325
|
+
retries += 1
|
|
4326
|
+
elif response.status_code in [502, 503, 504] and retries < 3:
|
|
4327
|
+
logger.warning(
|
|
4328
|
+
"M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
|
|
4329
|
+
response.status_code,
|
|
4330
|
+
(retries + 1) * 60,
|
|
4331
|
+
)
|
|
4332
|
+
time.sleep((retries + 1) * 60)
|
|
4333
|
+
retries += 1
|
|
4334
|
+
else:
|
|
4335
|
+
logger.error(
|
|
4336
|
+
"Cannot delete email -> %s from inbox of user -> %s; status -> %s; error -> %s",
|
|
4337
|
+
email_id,
|
|
4338
|
+
user_id,
|
|
4339
|
+
response.status_code,
|
|
4340
|
+
response.text,
|
|
4341
|
+
)
|
|
4342
|
+
return None
|
|
4343
|
+
|
|
4344
|
+
# end method definition
|
|
4345
|
+
|
|
4346
|
+
def email_verification(
|
|
4347
|
+
self,
|
|
4348
|
+
user_email: str,
|
|
4349
|
+
sender: str,
|
|
4350
|
+
subject: str,
|
|
4351
|
+
url_search_pattern: str,
|
|
4352
|
+
line_end_marker: str = "=",
|
|
4353
|
+
multi_line: bool = True,
|
|
4354
|
+
multi_line_end_marker: str = "%3D",
|
|
4355
|
+
replacements: list | None = None,
|
|
4356
|
+
max_retries: int = 6,
|
|
4357
|
+
use_browser_automation: bool = False,
|
|
4358
|
+
password: str = "",
|
|
4359
|
+
password_field_id: str = "",
|
|
4360
|
+
password_confirmation_field_id: str = "",
|
|
4361
|
+
password_submit_xpath: str = "",
|
|
4362
|
+
terms_of_service_xpath: str = "",
|
|
4363
|
+
) -> bool:
|
|
4364
|
+
"""Process email verification
|
|
4365
|
+
|
|
4366
|
+
Args:
|
|
4367
|
+
user_email (str): Email address of user recieving the verification mail.
|
|
4368
|
+
sender (str): Email sender (address)
|
|
4369
|
+
subject (str): Email subject to look for (can be substring)
|
|
4370
|
+
url_search_pattern (str): String the URL needs to contain to identify it.
|
|
4371
|
+
multi_line_end_marker (str): If the URL spans multiple lines this is the "end" marker for the last line.
|
|
4372
|
+
replacements (list): if the URL needs some treatment these replacements can be applied.
|
|
4373
|
+
Result:
|
|
4374
|
+
bool: True = Success, False = Failure
|
|
4375
|
+
"""
|
|
4376
|
+
|
|
4377
|
+
# Determine the M365 user for the current user by
|
|
4378
|
+
# the email address:
|
|
4379
|
+
m365_user = self.get_user(user_email=user_email)
|
|
4380
|
+
m365_user_id = self.get_result_value(m365_user, "id")
|
|
4381
|
+
if not m365_user_id:
|
|
4382
|
+
logger.warning("Cannot find M365 user -> %s", user_email)
|
|
4383
|
+
return False
|
|
4384
|
+
|
|
4385
|
+
if replacements is None:
|
|
4386
|
+
replacements = [{"from": "=3D", "to": "="}]
|
|
4387
|
+
|
|
4388
|
+
retries = 0
|
|
4389
|
+
while retries < max_retries:
|
|
4390
|
+
response = self.get_mail(
|
|
4391
|
+
user_id=m365_user_id,
|
|
4392
|
+
sender=sender,
|
|
4393
|
+
subject=subject,
|
|
4394
|
+
show_error=False,
|
|
4395
|
+
)
|
|
4396
|
+
if response and response["value"]:
|
|
4397
|
+
emails = response["value"]
|
|
4398
|
+
# potentially there may be multiple matching emails,
|
|
4399
|
+
# we want the most recent one (from today):
|
|
4400
|
+
latest_email = max(emails, key=lambda x: x["receivedDateTime"])
|
|
4401
|
+
# Extract just the date:
|
|
4402
|
+
latest_email_date = latest_email["receivedDateTime"].split("T")[0]
|
|
4403
|
+
# Get the current date (today):
|
|
4404
|
+
today_date = datetime.today().strftime("%Y-%m-%d")
|
|
4405
|
+
# We do a sanity check here: the verification mail should be from today,
|
|
4406
|
+
# otherwise we assume it is an old mail and we need to wait for the
|
|
4407
|
+
# new verification mail to yet arrive:
|
|
4408
|
+
if latest_email_date != today_date:
|
|
4409
|
+
logger.info(
|
|
4410
|
+
"Verification email not yet received (latest mail from -> %s). Waiting %s seconds...",
|
|
4411
|
+
latest_email_date,
|
|
4412
|
+
10 * (retries + 1),
|
|
4413
|
+
)
|
|
4414
|
+
time.sleep(10 * (retries + 1))
|
|
4415
|
+
retries += 1
|
|
4416
|
+
continue
|
|
4417
|
+
email_id = latest_email["id"]
|
|
4418
|
+
# The full email body needs to be loaded with a separate REST call:
|
|
4419
|
+
body_text = self.get_mail_body(user_id=m365_user_id, email_id=email_id)
|
|
4420
|
+
# Extract the verification URL.
|
|
4421
|
+
if body_text:
|
|
4422
|
+
url = self.extract_url_from_message_body(
|
|
4423
|
+
message_body=body_text,
|
|
4424
|
+
search_pattern=url_search_pattern,
|
|
4425
|
+
line_end_marker=line_end_marker,
|
|
4426
|
+
multi_line=multi_line,
|
|
4427
|
+
multi_line_end_marker=multi_line_end_marker,
|
|
4428
|
+
replacements=replacements,
|
|
4429
|
+
)
|
|
4430
|
+
else:
|
|
4431
|
+
url = ""
|
|
4432
|
+
if not url:
|
|
4433
|
+
logger.warning("Cannot find verification link in the email body!")
|
|
4434
|
+
return False
|
|
4435
|
+
# Simulate a "click" on this URL:
|
|
4436
|
+
if use_browser_automation:
|
|
4437
|
+
# Core Share needs a full browser:
|
|
4438
|
+
browser_automation_object = BrowserAutomation(
|
|
4439
|
+
take_screenshots=True,
|
|
4440
|
+
automation_name="email-verification",
|
|
4441
|
+
)
|
|
4442
|
+
logger.info(
|
|
4443
|
+
"Open URL -> %s to verify account or email change (using browser automation)",
|
|
4444
|
+
url,
|
|
4445
|
+
)
|
|
4446
|
+
success = browser_automation_object.get_page(url)
|
|
4447
|
+
if success:
|
|
4448
|
+
user_interaction_required = False
|
|
4449
|
+
logger.info(
|
|
4450
|
+
"Successfully opened URL. Browser title is -> '%s'.",
|
|
4451
|
+
browser_automation_object.get_title(),
|
|
4452
|
+
)
|
|
4453
|
+
if password_field_id:
|
|
4454
|
+
password_field = browser_automation_object.find_elem(
|
|
4455
|
+
find_elem=password_field_id, show_error=False
|
|
4456
|
+
)
|
|
4457
|
+
if password_field:
|
|
4458
|
+
# The subsequent processing is only required if
|
|
4459
|
+
# the returned page requests a password change:
|
|
4460
|
+
user_interaction_required = True
|
|
4461
|
+
logger.info(
|
|
4462
|
+
"Found password field on returned page - it seems email verification requests password entry!"
|
|
4463
|
+
)
|
|
4464
|
+
result = browser_automation_object.find_elem_and_set(
|
|
4465
|
+
find_elem=password_field_id,
|
|
4466
|
+
elem_value=password,
|
|
4467
|
+
is_sensitive=True,
|
|
4468
|
+
)
|
|
4469
|
+
if not result:
|
|
4470
|
+
logger.error(
|
|
4471
|
+
"Failed to enter password in field -> '%s'",
|
|
4472
|
+
password_field_id,
|
|
4473
|
+
)
|
|
4474
|
+
success = False
|
|
4475
|
+
else:
|
|
4476
|
+
logger.info(
|
|
4477
|
+
"No user interaction required (no password change or terms of service acceptance)."
|
|
4478
|
+
)
|
|
4479
|
+
if user_interaction_required and password_confirmation_field_id:
|
|
4480
|
+
password_confirm_field = (
|
|
4481
|
+
browser_automation_object.find_elem(
|
|
4482
|
+
find_elem=password_confirmation_field_id,
|
|
4483
|
+
show_error=False,
|
|
4484
|
+
)
|
|
4485
|
+
)
|
|
4486
|
+
if password_confirm_field:
|
|
4487
|
+
logger.info(
|
|
4488
|
+
"Found password confirmation field on returned page - it seems email verification requests consecutive password entry!"
|
|
4489
|
+
)
|
|
4490
|
+
result = browser_automation_object.find_elem_and_set(
|
|
4491
|
+
find_elem=password_confirmation_field_id,
|
|
4492
|
+
elem_value=password,
|
|
4493
|
+
is_sensitive=True,
|
|
4494
|
+
)
|
|
4495
|
+
if not result:
|
|
4496
|
+
logger.error(
|
|
4497
|
+
"Failed to enter password in field -> '%s'",
|
|
4498
|
+
password_confirmation_field_id,
|
|
4499
|
+
)
|
|
4500
|
+
success = False
|
|
4501
|
+
if user_interaction_required and password_submit_xpath:
|
|
4502
|
+
password_submit_button = (
|
|
4503
|
+
browser_automation_object.find_elem(
|
|
4504
|
+
find_elem=password_submit_xpath,
|
|
4505
|
+
find_method="xpath",
|
|
4506
|
+
show_error=False,
|
|
4507
|
+
)
|
|
4508
|
+
)
|
|
4509
|
+
if password_submit_button:
|
|
4510
|
+
logger.info(
|
|
4511
|
+
"Submit password change dialog with button -> '%s' (found with XPath -> %s)",
|
|
4512
|
+
password_submit_button.text,
|
|
4513
|
+
password_submit_xpath,
|
|
4514
|
+
)
|
|
4515
|
+
result = browser_automation_object.find_elem_and_click(
|
|
4516
|
+
find_elem=password_submit_xpath, find_method="xpath"
|
|
4517
|
+
)
|
|
4518
|
+
if not result:
|
|
4519
|
+
logger.error(
|
|
4520
|
+
"Failed to press submit button -> %s",
|
|
4521
|
+
password_submit_xpath,
|
|
4522
|
+
)
|
|
4523
|
+
success = False
|
|
4524
|
+
# TODO is this sleep required? The Terms of service dialog has some weird animation
|
|
4525
|
+
# which may require this. It seems it is rtequired!
|
|
4526
|
+
time.sleep(1)
|
|
4527
|
+
terms_accept_button = browser_automation_object.find_elem(
|
|
4528
|
+
find_elem=terms_of_service_xpath,
|
|
4529
|
+
find_method="xpath",
|
|
4530
|
+
show_error=False,
|
|
4531
|
+
)
|
|
4532
|
+
if terms_accept_button:
|
|
4533
|
+
logger.info(
|
|
4534
|
+
"Accept terms of service with button -> '%s' (found with XPath -> %s)",
|
|
4535
|
+
terms_accept_button.text,
|
|
4536
|
+
terms_of_service_xpath,
|
|
4537
|
+
)
|
|
4538
|
+
result = browser_automation_object.find_elem_and_click(
|
|
4539
|
+
find_elem=terms_of_service_xpath,
|
|
4540
|
+
find_method="xpath",
|
|
4541
|
+
)
|
|
4542
|
+
if not result:
|
|
4543
|
+
logger.error(
|
|
4544
|
+
"Failed to accept terms of service with button -> '%s'",
|
|
4545
|
+
terms_accept_button.text,
|
|
4546
|
+
)
|
|
4547
|
+
success = False
|
|
4548
|
+
else:
|
|
4549
|
+
logger.info("No Terms of Service acceptance required.")
|
|
4550
|
+
# end if use_browser_automation
|
|
4551
|
+
else:
|
|
4552
|
+
# Salesforce (other than Core Share) is OK with the simple HTTP GET request:
|
|
4553
|
+
logger.info("Open URL -> %s to verify account or email change", url)
|
|
4554
|
+
response = self._http_object.http_request(url=url, method="GET")
|
|
4555
|
+
success = response and response.ok
|
|
4556
|
+
|
|
4557
|
+
if success:
|
|
4558
|
+
logger.info("Remove email from inbox of user -> %s...", user_email)
|
|
4559
|
+
response = self.delete_mail(user_id=m365_user_id, email_id=email_id)
|
|
4560
|
+
if not response:
|
|
4561
|
+
logger.warning(
|
|
4562
|
+
"Couldn't remove the mail from the inbox of user -> %s",
|
|
4563
|
+
user_email,
|
|
4564
|
+
)
|
|
4565
|
+
# We have success now and can break from the while loop
|
|
4566
|
+
return True
|
|
4567
|
+
else:
|
|
4568
|
+
logger.error(
|
|
4569
|
+
"Failed to process e-mail verification for user -> %s",
|
|
4570
|
+
user_email,
|
|
4571
|
+
)
|
|
4572
|
+
return False
|
|
4573
|
+
# end if response and response["value"]
|
|
4574
|
+
else:
|
|
4575
|
+
logger.info(
|
|
4576
|
+
"Verification email not yet received (no mails with sender -> %s and subject -> '%s' found). Waiting %s seconds...",
|
|
4577
|
+
sender,
|
|
4578
|
+
subject,
|
|
4579
|
+
10 * (retries + 1),
|
|
4580
|
+
)
|
|
4581
|
+
time.sleep(10 * (retries + 1))
|
|
4582
|
+
retries += 1
|
|
4583
|
+
# end while
|
|
4584
|
+
|
|
4585
|
+
logger.warning(
|
|
4586
|
+
"Verification mail for user -> %s has not arrived in time.", user_email
|
|
4587
|
+
)
|
|
4588
|
+
|
|
4589
|
+
return False
|
|
4590
|
+
|
|
4591
|
+
# end method definition
|