pyxecm 1.5__py3-none-any.whl → 1.6__py3-none-any.whl

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

Potentially problematic release.


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

pyxecm/customizer/m365.py CHANGED
@@ -12,8 +12,10 @@ credentials_user: In some cases MS Graph APIs cannot be called via
12
12
  application permissions (client_id, client_secret)
13
13
  but requires a token of a user authenticated
14
14
  with username + password
15
+
15
16
  request_header: Returns the request header for MS Graph API calls
16
17
  request_header_user: Returns the request header used for user specific calls
18
+ do_request: Call an M365 Graph API in a safe way
17
19
  parse_request_response: Parse the REST API responses and convert
18
20
  them to Python dict in a safe way
19
21
  exist_result_item: Check if an dict item is in the response
@@ -102,9 +104,10 @@ import re
102
104
  import time
103
105
  import urllib.parse
104
106
  import zipfile
105
- from urllib.parse import quote
106
107
  from datetime import datetime
107
108
 
109
+ from urllib.parse import quote
110
+ from http import HTTPStatus
108
111
  import requests
109
112
 
110
113
  from pyxecm.helper.web import HTTP
@@ -118,6 +121,8 @@ request_login_headers = {
118
121
  }
119
122
 
120
123
  REQUEST_TIMEOUT = 60
124
+ REQUEST_RETRY_DELAY = 20
125
+ REQUEST_MAX_RETRIES = 3
121
126
 
122
127
  class M365(object):
123
128
  """Used to automate stettings in Microsoft 365 via the Graph API."""
@@ -268,6 +273,183 @@ class M365(object):
268
273
 
269
274
  # end method definition
270
275
 
276
+ def do_request(
277
+ self,
278
+ url: str,
279
+ method: str = "GET",
280
+ headers: dict | None = None,
281
+ data: dict | None = None,
282
+ json_data: dict | None = None,
283
+ files: dict | None = None,
284
+ params: dict | None = None,
285
+ timeout: int | None = REQUEST_TIMEOUT,
286
+ show_error: bool = True,
287
+ show_warning: bool = False,
288
+ warning_message: str = "",
289
+ failure_message: str = "",
290
+ success_message: str = "",
291
+ max_retries: int = REQUEST_MAX_RETRIES,
292
+ retry_forever: bool = False,
293
+ parse_request_response: bool = True,
294
+ stream: bool = False,
295
+ ) -> dict | None:
296
+ """Call an M365 Graph API in a safe way
297
+
298
+ Args:
299
+ url (str): URL to send the request to.
300
+ method (str, optional): HTTP method (GET, POST, etc.). Defaults to "GET".
301
+ headers (dict | None, optional): Request Headers. Defaults to None.
302
+ data (dict | None, optional): Request payload. Defaults to None
303
+ files (dict | None, optional): Dictionary of {"name": file-tuple} for multipart encoding upload.
304
+ file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
305
+ params (dict | None, optional): Add key-value pairs to the query string of the URL.
306
+ When you use the params parameter, requests automatically appends
307
+ the key-value pairs to the URL as part of the query string
308
+ timeout (int | None, optional): Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
309
+ show_error (bool, optional): Whether or not an error should be logged in case of a failed REST call.
310
+ If False, then only a warning is logged. Defaults to True.
311
+ warning_message (str, optional): Specific warning message. Defaults to "". If not given the error_message will be used.
312
+ failure_message (str, optional): Specific error message. Defaults to "".
313
+ success_message (str, optional): Specific success message. Defaults to "".
314
+ max_retries (int, optional): How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
315
+ retry_forever (bool, optional): Eventually wait forever - without timeout. Defaults to False.
316
+ parse_request_response (bool, optional): should the response.text be interpreted as json and loaded into a dictionary. True is the default.
317
+ stream (bool, optional): parameter is used to control whether the response content should be immediately downloaded or streamed incrementally
318
+
319
+ Returns:
320
+ dict | None: Response of OTDS REST API or None in case of an error.
321
+ """
322
+
323
+ if headers is None:
324
+ logger.error("Missing request header. Cannot send request to Core Share!")
325
+ return None
326
+
327
+ # In case of an expired session we reauthenticate and
328
+ # try 1 more time. Session expiration should not happen
329
+ # twice in a row:
330
+ retries = 0
331
+
332
+ while True:
333
+ try:
334
+ response = requests.request(
335
+ method=method,
336
+ url=url,
337
+ data=data,
338
+ json=json_data,
339
+ files=files,
340
+ params=params,
341
+ headers=headers,
342
+ timeout=timeout,
343
+ stream=stream,
344
+ )
345
+
346
+ if response.ok:
347
+ if success_message:
348
+ logger.info(success_message)
349
+ if parse_request_response:
350
+ return self.parse_request_response(response)
351
+ else:
352
+ return response
353
+ # Check if Session has expired - then re-authenticate and try once more
354
+ elif response.status_code == 401 and retries == 0:
355
+ logger.debug("Session has expired - try to re-authenticate...")
356
+ self.authenticate(revalidate=True)
357
+ headers = self.request_header()
358
+ retries += 1
359
+ elif (
360
+ response.status_code in [502, 503, 504]
361
+ and retries < REQUEST_MAX_RETRIES
362
+ ):
363
+ logger.warning(
364
+ "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
365
+ response.status_code,
366
+ (retries + 1) * 60,
367
+ )
368
+ time.sleep((retries + 1) * 60)
369
+ retries += 1
370
+ else:
371
+ # Handle plain HTML responses to not pollute the logs
372
+ content_type = response.headers.get("content-type", None)
373
+ if content_type == "text/html":
374
+ response_text = "HTML content (only printed in debug log)"
375
+ else:
376
+ response_text = response.text
377
+
378
+ if show_error:
379
+ logger.error(
380
+ "%s; status -> %s/%s; error -> %s",
381
+ failure_message,
382
+ response.status_code,
383
+ HTTPStatus(response.status_code).phrase,
384
+ response_text,
385
+ )
386
+ elif show_warning:
387
+ logger.warning(
388
+ "%s; status -> %s/%s; warning -> %s",
389
+ warning_message if warning_message else failure_message,
390
+ response.status_code,
391
+ HTTPStatus(response.status_code).phrase,
392
+ response_text,
393
+ )
394
+ if content_type == "text/html":
395
+ logger.debug(
396
+ "%s; status -> %s/%s; warning -> %s",
397
+ failure_message,
398
+ response.status_code,
399
+ HTTPStatus(response.status_code).phrase,
400
+ response.text,
401
+ )
402
+ return None
403
+ except requests.exceptions.Timeout:
404
+ if retries <= max_retries:
405
+ logger.warning(
406
+ "Request timed out. Retrying in %s seconds...",
407
+ str(REQUEST_RETRY_DELAY),
408
+ )
409
+ retries += 1
410
+ time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
411
+ else:
412
+ logger.error(
413
+ "%s; timeout error",
414
+ failure_message,
415
+ )
416
+ if retry_forever:
417
+ # If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
418
+ logger.warning("Turn timeouts off and wait forever...")
419
+ timeout = None
420
+ else:
421
+ return None
422
+ except requests.exceptions.ConnectionError:
423
+ if retries <= max_retries:
424
+ logger.warning(
425
+ "Connection error. Retrying in %s seconds...",
426
+ str(REQUEST_RETRY_DELAY),
427
+ )
428
+ retries += 1
429
+ time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
430
+ else:
431
+ logger.error(
432
+ "%s; connection error",
433
+ failure_message,
434
+ )
435
+ if retry_forever:
436
+ # If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
437
+ logger.warning("Turn timeouts off and wait forever...")
438
+ timeout = None
439
+ time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
440
+ else:
441
+ return None
442
+ # end try
443
+ logger.debug(
444
+ "Retrying REST API %s call -> %s... (retry = %s)",
445
+ method,
446
+ url,
447
+ str(retries),
448
+ )
449
+ # end while True
450
+
451
+ # end method definition
452
+
271
453
  def parse_request_response(
272
454
  self,
273
455
  response_object: requests.Response,
@@ -518,7 +700,7 @@ class M365(object):
518
700
  logger.debug("User Access Token -> %s", access_token)
519
701
  else:
520
702
  logger.error(
521
- "Failed to request an M365 Access Token for user -> %s; error -> %s",
703
+ "Failed to request an M365 Access Token for user -> '%s'; error -> %s",
522
704
  username,
523
705
  authenticate_response.text,
524
706
  )
@@ -535,42 +717,21 @@ class M365(object):
535
717
  """Get list all all users in M365 tenant
536
718
 
537
719
  Returns:
538
- dict: Dictionary of all users.
720
+ dict: Dictionary of all M365 users.
539
721
  """
540
722
 
541
723
  request_url = self.config()["usersUrl"]
542
724
  request_header = self.request_header()
543
725
 
544
- logger.debug("Get list of all users; calling -> %s", request_url)
726
+ logger.debug("Get list of all M365 users; calling -> %s", request_url)
545
727
 
546
- retries = 0
547
- while True:
548
- response = requests.get(
549
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
550
- )
551
- if response.ok:
552
- return self.parse_request_response(response)
553
- # Check if Session has expired - then re-authenticate and try once more
554
- elif response.status_code == 401 and retries == 0:
555
- logger.debug("Session has expired - try to re-authenticate...")
556
- self.authenticate(revalidate=True)
557
- request_header = self.request_header()
558
- retries += 1
559
- elif response.status_code in [502, 503, 504] and retries < 3:
560
- logger.warning(
561
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
562
- response.status_code,
563
- (retries + 1) * 60,
564
- )
565
- time.sleep((retries + 1) * 60)
566
- retries += 1
567
- else:
568
- logger.error(
569
- "Failed to get list of users; status -> %s; error -> %s",
570
- response.status_code,
571
- response.text,
572
- )
573
- return None
728
+ return self.do_request(
729
+ url=request_url,
730
+ method="GET",
731
+ headers=request_header,
732
+ timeout=REQUEST_TIMEOUT,
733
+ failure_message="Failed to get list of M365 users!",
734
+ )
574
735
 
575
736
  # end method definition
576
737
 
@@ -631,38 +792,14 @@ class M365(object):
631
792
 
632
793
  logger.debug("Get M365 user -> %s; calling -> %s", user_email, request_url)
633
794
 
634
- retries = 0
635
- while True:
636
- response = requests.get(
637
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
638
- )
639
- if response.ok:
640
- return self.parse_request_response(response)
641
- # Check if Session has expired - then re-authenticate and try once more
642
- elif response.status_code == 401 and retries == 0:
643
- logger.debug("Session has expired - try to re-authenticate...")
644
- self.authenticate(revalidate=True)
645
- request_header = self.request_header()
646
- retries += 1
647
- elif response.status_code in [502, 503, 504] and retries < 3:
648
- logger.warning(
649
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
650
- response.status_code,
651
- (retries + 1) * 60,
652
- )
653
- time.sleep((retries + 1) * 60)
654
- retries += 1
655
- else:
656
- if show_error:
657
- logger.error(
658
- "Failed to get M365 user -> %s; status -> %s; error -> %s",
659
- user_email,
660
- response.status_code,
661
- response.text,
662
- )
663
- else:
664
- logger.debug("M365 User -> %s not found.", user_email)
665
- return None
795
+ return self.do_request(
796
+ url=request_url,
797
+ method="GET",
798
+ headers=request_header,
799
+ timeout=REQUEST_TIMEOUT,
800
+ failure_message="Failed to get M365 user -> '{}'".format(user_email),
801
+ show_error=show_error,
802
+ )
666
803
 
667
804
  # end method definition
668
805
 
@@ -714,38 +851,14 @@ class M365(object):
714
851
 
715
852
  logger.debug("Adding M365 user -> %s; calling -> %s", email, request_url)
716
853
 
717
- retries = 0
718
- while True:
719
- response = requests.post(
720
- request_url,
721
- data=json.dumps(user_post_body),
722
- headers=request_header,
723
- timeout=REQUEST_TIMEOUT,
724
- )
725
- if response.ok:
726
- return self.parse_request_response(response)
727
- # Check if Session has expired - then re-authenticate and try once more
728
- elif response.status_code == 401 and retries == 0:
729
- logger.debug("Session has expired - try to re-authenticate...")
730
- self.authenticate(revalidate=True)
731
- request_header = self.request_header()
732
- retries += 1
733
- elif response.status_code in [502, 503, 504] and retries < 3:
734
- logger.warning(
735
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
736
- response.status_code,
737
- (retries + 1) * 60,
738
- )
739
- time.sleep((retries + 1) * 60)
740
- retries += 1
741
- else:
742
- logger.error(
743
- "Failed to add M365 user -> %s; status -> %s; error -> %s",
744
- email,
745
- response.status_code,
746
- response.text,
747
- )
748
- return None
854
+ return self.do_request(
855
+ url=request_url,
856
+ method="POST",
857
+ headers=request_header,
858
+ data=json.dumps(user_post_body),
859
+ timeout=REQUEST_TIMEOUT,
860
+ failure_message="Failed to add M365 user -> '{}'".format(email),
861
+ )
749
862
 
750
863
  # end method definition
751
864
 
@@ -770,39 +883,16 @@ class M365(object):
770
883
  request_url,
771
884
  )
772
885
 
773
- retries = 0
774
- while True:
775
- response = requests.patch(
776
- request_url,
777
- json=updated_settings,
778
- headers=request_header,
779
- timeout=REQUEST_TIMEOUT,
780
- )
781
- if response.ok:
782
- return self.parse_request_response(response)
783
- # Check if Session has expired - then re-authenticate and try once more
784
- elif response.status_code == 401 and retries == 0:
785
- logger.debug("Session has expired - try to re-authenticate...")
786
- self.authenticate(revalidate=True)
787
- request_header = self.request_header()
788
- retries += 1
789
- elif response.status_code in [502, 503, 504] and retries < 3:
790
- logger.warning(
791
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
792
- response.status_code,
793
- (retries + 1) * 60,
794
- )
795
- time.sleep((retries + 1) * 60)
796
- retries += 1
797
- else:
798
- logger.error(
799
- "Failed to update M365 user -> %s with -> %s; status -> %s; error -> %s",
800
- user_id,
801
- str(updated_settings),
802
- response.status_code,
803
- response.text,
804
- )
805
- return None
886
+ return self.do_request(
887
+ url=request_url,
888
+ method="PATCH",
889
+ headers=request_header,
890
+ json_data=updated_settings,
891
+ timeout=REQUEST_TIMEOUT,
892
+ failure_message="Failed to update M365 user -> '{}' with -> {}".format(
893
+ user_id, updated_settings
894
+ ),
895
+ )
806
896
 
807
897
  # end method definition
808
898
 
@@ -831,35 +921,13 @@ class M365(object):
831
921
  request_url = self.config()["usersUrl"] + "/" + user_id + "/licenseDetails"
832
922
  request_header = self.request_header()
833
923
 
834
- retries = 0
835
- while True:
836
- response = requests.get(
837
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
838
- )
839
- if response.ok:
840
- return self.parse_request_response(response)
841
- # Check if Session has expired - then re-authenticate and try once more
842
- elif response.status_code == 401 and retries == 0:
843
- logger.debug("Session has expired - try to re-authenticate...")
844
- self.authenticate(revalidate=True)
845
- request_header = self.request_header()
846
- retries += 1
847
- elif response.status_code in [502, 503, 504] and retries < 3:
848
- logger.warning(
849
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
850
- response.status_code,
851
- (retries + 1) * 60,
852
- )
853
- time.sleep((retries + 1) * 60)
854
- retries += 1
855
- else:
856
- logger.error(
857
- "Failed to get M365 licenses of user -> %s; status -> %s; error -> %s",
858
- user_id,
859
- response.status_code,
860
- response.text,
861
- )
862
- return None
924
+ return self.do_request(
925
+ url=request_url,
926
+ method="GET",
927
+ headers=request_header,
928
+ timeout=REQUEST_TIMEOUT,
929
+ failure_message="Failed to get M365 licenses of user -> {}".format(user_id),
930
+ )
863
931
 
864
932
  # end method definition
865
933
 
@@ -897,39 +965,16 @@ class M365(object):
897
965
  request_url,
898
966
  )
899
967
 
900
- retries = 0
901
- while True:
902
- response = requests.post(
903
- request_url,
904
- json=license_post_body,
905
- headers=request_header,
906
- timeout=REQUEST_TIMEOUT,
907
- )
908
- if response.ok:
909
- return self.parse_request_response(response)
910
- # Check if Session has expired - then re-authenticate and try once more
911
- elif response.status_code == 401 and retries == 0:
912
- logger.debug("Session has expired - try to re-authenticate...")
913
- self.authenticate(revalidate=True)
914
- request_header = self.request_header()
915
- retries += 1
916
- elif response.status_code in [502, 503, 504] and retries < 3:
917
- logger.warning(
918
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
919
- response.status_code,
920
- (retries + 1) * 60,
921
- )
922
- time.sleep((retries + 1) * 60)
923
- retries += 1
924
- else:
925
- logger.error(
926
- "Failed to add M365 license -> %s to M365 user -> %s; status -> %s; error -> %s",
927
- sku_id,
928
- user_id,
929
- response.status_code,
930
- response.text,
931
- )
932
- return None
968
+ return self.do_request(
969
+ url=request_url,
970
+ method="POST",
971
+ headers=request_header,
972
+ json_data=license_post_body,
973
+ timeout=REQUEST_TIMEOUT,
974
+ failure_message="Failed to add M365 license -> {} to M365 user -> {}".format(
975
+ sku_id, user_id
976
+ ),
977
+ )
933
978
 
934
979
  # end method definition
935
980
 
@@ -950,42 +995,27 @@ class M365(object):
950
995
 
951
996
  logger.debug("Get photo of user -> %s; calling -> %s", user_id, request_url)
952
997
 
953
- retries = 0
954
- while True:
955
- response = requests.get(
956
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
957
- )
958
- if response.ok:
959
- return response.content # this is the actual image - not json!
960
- # Check if Session has expired - then re-authenticate and try once more
961
- elif response.status_code == 401 and retries == 0:
962
- logger.debug("Session has expired - try to re-authenticate...")
963
- self.authenticate(revalidate=True)
964
- request_header = self.request_header()
965
- retries += 1
966
- elif response.status_code in [502, 503, 504] and retries < 3:
967
- logger.warning(
968
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
969
- response.status_code,
970
- (retries + 1) * 60,
971
- )
972
- time.sleep((retries + 1) * 60)
973
- retries += 1
974
- else:
975
- if show_error:
976
- logger.error(
977
- "Failed to get photo of user -> %s; status -> %s; error -> %s",
978
- user_id,
979
- response.status_code,
980
- response.text,
981
- )
982
- else:
983
- logger.debug("M365 User -> %s does not yet have a photo.", user_id)
984
- return None
998
+ response = self.do_request(
999
+ url=request_url,
1000
+ method="GET",
1001
+ headers=request_header,
1002
+ timeout=REQUEST_TIMEOUT,
1003
+ failure_message="Failed to get photo of M365 user -> {}".format(user_id),
1004
+ warning_message="M365 User -> {} does not yet have a photo.".format(
1005
+ user_id
1006
+ ),
1007
+ show_error=show_error,
1008
+ parse_request_response=False, # the response is NOT JSON!
1009
+ )
1010
+
1011
+ if response and response.ok and response.content:
1012
+ return response.content # this is the actual image - not json!
1013
+
1014
+ return None
985
1015
 
986
1016
  # end method definition
987
1017
 
988
- def download_user_photo(self, user_id: str, photo_path: str) -> str:
1018
+ def download_user_photo(self, user_id: str, photo_path: str) -> str | None:
989
1019
  """Download the M365 user photo and save it to the local file system
990
1020
 
991
1021
  Args:
@@ -1005,64 +1035,48 @@ class M365(object):
1005
1035
  request_url,
1006
1036
  )
1007
1037
 
1008
- retries = 0
1009
- while True:
1010
- response = requests.get(
1011
- request_url,
1012
- headers=request_header,
1013
- timeout=REQUEST_TIMEOUT,
1014
- stream=True,
1038
+ response = self.do_request(
1039
+ url=request_url,
1040
+ method="GET",
1041
+ headers=request_header,
1042
+ timeout=REQUEST_TIMEOUT,
1043
+ failure_message="Failed to download photo for user with ID -> {}".format(
1044
+ user_id
1045
+ ),
1046
+ stream=True,
1047
+ parse_request_response=False,
1048
+ )
1049
+
1050
+ if response and response.ok:
1051
+ content_type = response.headers.get("Content-Type", "image/png")
1052
+ if content_type == "image/jpeg":
1053
+ file_extension = "jpg"
1054
+ elif content_type == "image/png":
1055
+ file_extension = "png"
1056
+ else:
1057
+ file_extension = "img" # Default extension if type is unknown
1058
+ file_path = os.path.join(
1059
+ photo_path, "{}.{}".format(user_id, file_extension)
1015
1060
  )
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
1061
 
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,
1062
+ try:
1063
+ with open(file_path, "wb") as file:
1064
+ for chunk in response.iter_content(chunk_size=8192):
1065
+ file.write(chunk)
1066
+ logger.info(
1067
+ "Photo for M365 user with ID -> %s saved to -> '%s'",
1068
+ user_id,
1069
+ file_path,
1055
1070
  )
1056
- time.sleep((retries + 1) * 60)
1057
- retries += 1
1058
- else:
1071
+ return file_path
1072
+ except OSError as exception:
1059
1073
  logger.error(
1060
- "Failed to download photo for user with ID -> %s; status -> %s; error -> %s",
1074
+ "Error saving photo for user with ID -> %s; error -> %s",
1061
1075
  user_id,
1062
- response.status_code,
1063
- response.text,
1076
+ exception,
1064
1077
  )
1065
- return None
1078
+
1079
+ return None
1066
1080
 
1067
1081
  # end method definition
1068
1082
 
@@ -1105,36 +1119,16 @@ class M365(object):
1105
1119
  request_url,
1106
1120
  )
1107
1121
 
1108
- retries = 0
1109
- while True:
1110
- response = requests.put(
1111
- request_url, headers=request_header, data=data, timeout=REQUEST_TIMEOUT
1112
- )
1113
- if response.ok:
1114
- return self.parse_request_response(response)
1115
- # Check if Session has expired - then re-authenticate and try once more
1116
- elif response.status_code == 401 and retries == 0:
1117
- logger.debug("Session has expired - try to re-authenticate...")
1118
- self.authenticate(revalidate=True)
1119
- request_header = self.request_header()
1120
- retries += 1
1121
- elif response.status_code in [502, 503, 504] and retries < 3:
1122
- logger.warning(
1123
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1124
- response.status_code,
1125
- (retries + 1) * 60,
1126
- )
1127
- time.sleep((retries + 1) * 60)
1128
- retries += 1
1129
- else:
1130
- logger.error(
1131
- "Failed to update user with ID -> %s with photo -> %s; status -> %s; error -> %s",
1132
- user_id,
1133
- photo_path,
1134
- response.status_code,
1135
- response.text,
1136
- )
1137
- return None
1122
+ return self.do_request(
1123
+ url=request_url,
1124
+ method="PUT",
1125
+ headers=request_header,
1126
+ data=data,
1127
+ timeout=REQUEST_TIMEOUT,
1128
+ failure_message="Failed to update M365 user with ID -> {} with photo -> '{}'".format(
1129
+ user_id, photo_path
1130
+ ),
1131
+ )
1138
1132
 
1139
1133
  # end method definition
1140
1134
 
@@ -1152,37 +1146,14 @@ class M365(object):
1152
1146
 
1153
1147
  logger.debug("Get list of all M365 groups; calling -> %s", request_url)
1154
1148
 
1155
- retries = 0
1156
- while True:
1157
- response = requests.get(
1158
- request_url,
1159
- headers=request_header,
1160
- params={"$top": str(max_number)},
1161
- timeout=REQUEST_TIMEOUT,
1162
- )
1163
- if response.ok:
1164
- return self.parse_request_response(response)
1165
- # Check if Session has expired - then re-authenticate and try once more
1166
- elif response.status_code == 401 and retries == 0:
1167
- logger.debug("Session has expired - try to re-authenticate...")
1168
- self.authenticate(revalidate=True)
1169
- request_header = self.request_header()
1170
- retries += 1
1171
- elif response.status_code in [502, 503, 504] and retries < 3:
1172
- logger.warning(
1173
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1174
- response.status_code,
1175
- (retries + 1) * 60,
1176
- )
1177
- time.sleep((retries + 1) * 60)
1178
- retries += 1
1179
- else:
1180
- logger.error(
1181
- "Failed to get list of M365 groups; status -> %s; error -> %s",
1182
- response.status_code,
1183
- response.text,
1184
- )
1185
- return None
1149
+ return self.do_request(
1150
+ url=request_url,
1151
+ method="GET",
1152
+ headers=request_header,
1153
+ params={"$top": str(max_number)},
1154
+ timeout=REQUEST_TIMEOUT,
1155
+ failure_message="Failed to get list of M365 groups",
1156
+ )
1186
1157
 
1187
1158
  # end method definition
1188
1159
 
@@ -1247,40 +1218,16 @@ class M365(object):
1247
1218
  request_url = self.config()["groupsUrl"] + "?" + encoded_query
1248
1219
  request_header = self.request_header()
1249
1220
 
1250
- logger.debug("Get M365 group -> %s; calling -> %s", group_name, request_url)
1221
+ logger.debug("Get M365 group -> '%s'; calling -> %s", group_name, request_url)
1251
1222
 
1252
- retries = 0
1253
- while True:
1254
- response = requests.get(
1255
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
1256
- )
1257
- if response.ok:
1258
- return self.parse_request_response(response)
1259
- # Check if Session has expired - then re-authenticate and try once more
1260
- elif response.status_code == 401 and retries == 0:
1261
- logger.debug("Session has expired - try to re-authenticate...")
1262
- self.authenticate(revalidate=True)
1263
- request_header = self.request_header()
1264
- retries += 1
1265
- elif response.status_code in [502, 503, 504] and retries < 3:
1266
- logger.warning(
1267
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1268
- response.status_code,
1269
- (retries + 1) * 60,
1270
- )
1271
- time.sleep((retries + 1) * 60)
1272
- retries += 1
1273
- else:
1274
- if show_error:
1275
- logger.error(
1276
- "Failed to get M365 group -> %s; status -> %s; error -> %s",
1277
- group_name,
1278
- response.status_code,
1279
- response.text,
1280
- )
1281
- else:
1282
- logger.debug("M365 Group -> %s not found.", group_name)
1283
- return None
1223
+ return self.do_request(
1224
+ url=request_url,
1225
+ method="GET",
1226
+ headers=request_header,
1227
+ timeout=REQUEST_TIMEOUT,
1228
+ failure_message="Failed to get M365 group -> '{}'".format(group_name),
1229
+ show_error=show_error,
1230
+ )
1284
1231
 
1285
1232
  # end method definition
1286
1233
 
@@ -1345,41 +1292,17 @@ class M365(object):
1345
1292
  request_url = self.config()["groupsUrl"]
1346
1293
  request_header = self.request_header()
1347
1294
 
1348
- logger.debug("Adding M365 group -> %s; calling -> %s", name, request_url)
1349
- logger.debug("M365 group attributes -> %s", group_post_body)
1350
-
1351
- retries = 0
1352
- while True:
1353
- response = requests.post(
1354
- request_url,
1355
- data=json.dumps(group_post_body),
1356
- headers=request_header,
1357
- timeout=REQUEST_TIMEOUT,
1358
- )
1359
- if response.ok:
1360
- return self.parse_request_response(response)
1361
- # Check if Session has expired - then re-authenticate and try once more
1362
- elif response.status_code == 401 and retries == 0:
1363
- logger.debug("Session has expired - try to re-authenticate...")
1364
- self.authenticate(revalidate=True)
1365
- request_header = self.request_header()
1366
- retries += 1
1367
- elif response.status_code in [502, 503, 504] and retries < 3:
1368
- logger.warning(
1369
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1370
- response.status_code,
1371
- (retries + 1) * 60,
1372
- )
1373
- time.sleep((retries + 1) * 60)
1374
- retries += 1
1375
- else:
1376
- logger.error(
1377
- "Failed to add M365 group -> %s; status -> %s; error -> %s",
1378
- name,
1379
- response.status_code,
1380
- response.text,
1381
- )
1382
- return None
1295
+ logger.debug("Adding M365 group -> '%s'; calling -> %s", name, request_url)
1296
+ logger.debug("M365 group attributes -> %s", str(group_post_body))
1297
+
1298
+ return self.do_request(
1299
+ url=request_url,
1300
+ method="POST",
1301
+ headers=request_header,
1302
+ data=json.dumps(group_post_body),
1303
+ timeout=REQUEST_TIMEOUT,
1304
+ failure_message="Failed to add M365 group -> '{}'".format(name),
1305
+ )
1383
1306
 
1384
1307
  # end method definition
1385
1308
 
@@ -1416,36 +1339,15 @@ class M365(object):
1416
1339
  request_url,
1417
1340
  )
1418
1341
 
1419
- retries = 0
1420
- while True:
1421
- response = requests.get(
1422
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
1423
- )
1424
- if response.ok:
1425
- return self.parse_request_response(response)
1426
- # Check if Session has expired - then re-authenticate and try once more
1427
- elif response.status_code == 401 and retries == 0:
1428
- logger.debug("Session has expired - try to re-authenticate...")
1429
- self.authenticate(revalidate=True)
1430
- request_header = self.request_header()
1431
- retries += 1
1432
- elif response.status_code in [502, 503, 504] and retries < 3:
1433
- logger.warning(
1434
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1435
- response.status_code,
1436
- (retries + 1) * 60,
1437
- )
1438
- time.sleep((retries + 1) * 60)
1439
- retries += 1
1440
- else:
1441
- logger.error(
1442
- "Failed to get members of M365 group -> %s (%s); status -> %s; error -> %s",
1443
- group_name,
1444
- group_id,
1445
- response.status_code,
1446
- response.text,
1447
- )
1448
- return None
1342
+ return self.do_request(
1343
+ url=request_url,
1344
+ method="GET",
1345
+ headers=request_header,
1346
+ timeout=REQUEST_TIMEOUT,
1347
+ failure_message="Failed to get members of M365 group -> '{}' ({})".format(
1348
+ group_name, group_id
1349
+ ),
1350
+ )
1449
1351
 
1450
1352
  # end method definition
1451
1353
 
@@ -1473,40 +1375,16 @@ class M365(object):
1473
1375
  request_url,
1474
1376
  )
1475
1377
 
1476
- retries = 0
1477
- while True:
1478
- response = requests.post(
1479
- request_url,
1480
- headers=request_header,
1481
- data=json.dumps(group_member_post_body),
1482
- timeout=REQUEST_TIMEOUT,
1483
- )
1484
- if response.ok:
1485
- return self.parse_request_response(response)
1486
-
1487
- # Check if Session has expired - then re-authenticate and try once more
1488
- if response.status_code == 401 and retries == 0:
1489
- logger.debug("Session has expired - try to re-authenticate...")
1490
- self.authenticate(revalidate=True)
1491
- request_header = self.request_header()
1492
- retries += 1
1493
- elif response.status_code in [502, 503, 504] and retries < 3:
1494
- logger.warning(
1495
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1496
- response.status_code,
1497
- (retries + 1) * 60,
1498
- )
1499
- time.sleep((retries + 1) * 60)
1500
- retries += 1
1501
- else:
1502
- logger.error(
1503
- "Failed to add member -> %s to M365 group -> %s; status -> %s; error -> %s",
1504
- member_id,
1505
- group_id,
1506
- response.status_code,
1507
- response.text,
1508
- )
1509
- return None
1378
+ return self.do_request(
1379
+ url=request_url,
1380
+ method="POST",
1381
+ headers=request_header,
1382
+ data=json.dumps(group_member_post_body),
1383
+ timeout=REQUEST_TIMEOUT,
1384
+ failure_message="Failed to add member -> {} to M365 group -> {}".format(
1385
+ member_id, group_id
1386
+ ),
1387
+ )
1510
1388
 
1511
1389
  # end method definition
1512
1390
 
@@ -1536,42 +1414,21 @@ class M365(object):
1536
1414
  request_url,
1537
1415
  )
1538
1416
 
1539
- retries = 0
1540
- while True:
1541
- response = requests.get(
1542
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
1543
- )
1544
- if response.ok:
1545
- response = self.parse_request_response(response)
1546
- if not "value" in response or len(response["value"]) == 0:
1547
- return False
1548
- return True
1549
- # Check if Session has expired - then re-authenticate and try once more
1550
- elif response.status_code == 401 and retries == 0:
1551
- logger.debug("Session has expired - try to re-authenticate...")
1552
- self.authenticate(revalidate=True)
1553
- request_header = self.request_header()
1554
- retries += 1
1555
- elif response.status_code in [502, 503, 504] and retries < 3:
1556
- logger.warning(
1557
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1558
- response.status_code,
1559
- (retries + 1) * 60,
1560
- )
1561
- time.sleep((retries + 1) * 60)
1562
- retries += 1
1563
- else:
1564
- # MS Graph API returns an error if the member is not in the
1565
- # group. This is typically not what we want. We just return False.
1566
- if show_error:
1567
- logger.error(
1568
- "Failed to check if user -> %s is in group -> %s; status -> %s; error -> %s",
1569
- member_id,
1570
- group_id,
1571
- response.status_code,
1572
- response.text,
1573
- )
1574
- return False
1417
+ response = self.do_request(
1418
+ url=request_url,
1419
+ method="GET",
1420
+ headers=request_header,
1421
+ timeout=REQUEST_TIMEOUT,
1422
+ failure_message="Failed to check if user -> {} is in group -> {}".format(
1423
+ member_id, group_id
1424
+ ),
1425
+ show_error=show_error,
1426
+ )
1427
+
1428
+ if not response or not "value" in response or len(response["value"]) == 0:
1429
+ return False
1430
+
1431
+ return True
1575
1432
 
1576
1433
  # end method definition
1577
1434
 
@@ -1608,36 +1465,15 @@ class M365(object):
1608
1465
  request_url,
1609
1466
  )
1610
1467
 
1611
- retries = 0
1612
- while True:
1613
- response = requests.get(
1614
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
1615
- )
1616
- if response.ok:
1617
- return self.parse_request_response(response)
1618
- # Check if Session has expired - then re-authenticate and try once more
1619
- elif response.status_code == 401 and retries == 0:
1620
- logger.debug("Session has expired - try to re-authenticate...")
1621
- self.authenticate(revalidate=True)
1622
- request_header = self.request_header()
1623
- retries += 1
1624
- elif response.status_code in [502, 503, 504] and retries < 3:
1625
- logger.warning(
1626
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1627
- response.status_code,
1628
- (retries + 1) * 60,
1629
- )
1630
- time.sleep((retries + 1) * 60)
1631
- retries += 1
1632
- else:
1633
- logger.error(
1634
- "Failed to get owners of M365 group -> %s (%s); status -> %s; error -> %s",
1635
- group_name,
1636
- group_id,
1637
- response.status_code,
1638
- response.text,
1639
- )
1640
- return None
1468
+ return self.do_request(
1469
+ url=request_url,
1470
+ method="GET",
1471
+ headers=request_header,
1472
+ timeout=REQUEST_TIMEOUT,
1473
+ failure_message="Failed to get owners of M365 group -> '{}' ({})".format(
1474
+ group_name, group_id
1475
+ ),
1476
+ )
1641
1477
 
1642
1478
  # end method definition
1643
1479
 
@@ -1665,39 +1501,16 @@ class M365(object):
1665
1501
  request_url,
1666
1502
  )
1667
1503
 
1668
- retries = 0
1669
- while True:
1670
- response = requests.post(
1671
- request_url,
1672
- headers=request_header,
1673
- data=json.dumps(group_member_post_body),
1674
- timeout=REQUEST_TIMEOUT,
1675
- )
1676
- if response.ok:
1677
- return self.parse_request_response(response)
1678
- # Check if Session has expired - then re-authenticate and try once more
1679
- elif response.status_code == 401 and retries == 0:
1680
- logger.debug("Session has expired - try to re-authenticate...")
1681
- self.authenticate(revalidate=True)
1682
- request_header = self.request_header()
1683
- retries += 1
1684
- elif response.status_code in [502, 503, 504] and retries < 3:
1685
- logger.warning(
1686
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1687
- response.status_code,
1688
- (retries + 1) * 60,
1689
- )
1690
- time.sleep((retries + 1) * 60)
1691
- retries += 1
1692
- else:
1693
- logger.error(
1694
- "Failed to add owner -> %s to M365 group -> %s; status -> %s; error -> %s",
1695
- owner_id,
1696
- group_id,
1697
- response.status_code,
1698
- response.text,
1699
- )
1700
- return None
1504
+ return self.do_request(
1505
+ url=request_url,
1506
+ method="POST",
1507
+ headers=request_header,
1508
+ data=json.dumps(group_member_post_body),
1509
+ timeout=REQUEST_TIMEOUT,
1510
+ failure_message="Failed to add owner -> {} to M365 group -> {}".format(
1511
+ owner_id, group_id
1512
+ ),
1513
+ )
1701
1514
 
1702
1515
  # end method definition
1703
1516
 
@@ -1751,35 +1564,13 @@ class M365(object):
1751
1564
 
1752
1565
  logger.debug("Purging deleted item -> %s; calling -> %s", item_id, request_url)
1753
1566
 
1754
- retries = 0
1755
- while True:
1756
- response = requests.delete(
1757
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
1758
- )
1759
- if response.ok:
1760
- return self.parse_request_response(response)
1761
- # Check if Session has expired - then re-authenticate and try once more
1762
- elif response.status_code == 401 and retries == 0:
1763
- logger.debug("Session has expired - try to re-authenticate...")
1764
- self.authenticate(revalidate=True)
1765
- request_header = self.request_header()
1766
- retries += 1
1767
- elif response.status_code in [502, 503, 504] and retries < 3:
1768
- logger.warning(
1769
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1770
- response.status_code,
1771
- (retries + 1) * 60,
1772
- )
1773
- time.sleep((retries + 1) * 60)
1774
- retries += 1
1775
- else:
1776
- logger.error(
1777
- "Failed to purge deleted item -> %s; status -> %s; error -> %s",
1778
- item_id,
1779
- response.status_code,
1780
- response.text,
1781
- )
1782
- return None
1567
+ return self.do_request(
1568
+ url=request_url,
1569
+ method="DELETE",
1570
+ headers=request_header,
1571
+ timeout=REQUEST_TIMEOUT,
1572
+ failure_message="Failed to purge deleted item -> {}".format(item_id),
1573
+ )
1783
1574
 
1784
1575
  # end method definition
1785
1576
 
@@ -1810,39 +1601,27 @@ class M365(object):
1810
1601
  request_url,
1811
1602
  )
1812
1603
 
1813
- retries = 0
1814
- while True:
1815
- response = requests.get(
1816
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
1817
- )
1604
+ response = self.do_request(
1605
+ url=request_url,
1606
+ method="GET",
1607
+ headers=request_header,
1608
+ timeout=REQUEST_TIMEOUT,
1609
+ failure_message="Failed to check if M365 Group -> '{}' has a M365 Team connected".format(
1610
+ group_name
1611
+ ),
1612
+ parse_request_response=False,
1613
+ )
1818
1614
 
1819
- if response.status_code == 200: # Group has a Team assigned!
1820
- logger.debug("Group -> %s has a M365 Team connected.", group_name)
1821
- return True
1822
- elif response.status_code == 404: # Group does not have a Team assigned!
1823
- logger.debug("Group -> %s has no M365 Team connected.", group_name)
1824
- return False
1825
- elif response.status_code == 401 and retries == 0:
1826
- logger.debug("Session has expired - try to re-authenticate...")
1827
- self.authenticate(revalidate=True)
1828
- request_header = self.request_header()
1829
- retries += 1
1830
- elif response.status_code in [502, 503, 504] and retries < 3:
1831
- logger.warning(
1832
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1833
- response.status_code,
1834
- (retries + 1) * 60,
1835
- )
1836
- time.sleep((retries + 1) * 60)
1837
- retries += 1
1838
- else:
1839
- logger.error(
1840
- "Failed to check if M365 Group -> %s has a M365 Team connected; status -> %s; error -> %s",
1841
- group_name,
1842
- response.status_code,
1843
- response.text,
1844
- )
1845
- return False
1615
+ if response and response.status_code == 200: # Group has a Team assigned!
1616
+ logger.debug("Group -> %s has a M365 Team connected.", group_name)
1617
+ return True
1618
+ elif (
1619
+ not response or response.status_code == 404
1620
+ ): # Group does not have a Team assigned!
1621
+ logger.debug("Group -> %s has no M365 Team connected.", group_name)
1622
+ return False
1623
+
1624
+ return False
1846
1625
 
1847
1626
  # end method definition
1848
1627
 
@@ -1902,35 +1681,13 @@ class M365(object):
1902
1681
  request_url,
1903
1682
  )
1904
1683
 
1905
- retries = 0
1906
- while True:
1907
- response = requests.get(
1908
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
1909
- )
1910
- if response.ok:
1911
- return self.parse_request_response(response)
1912
- # Check if Session has expired - then re-authenticate and try once more
1913
- elif response.status_code == 401 and retries == 0:
1914
- logger.debug("Session has expired - try to re-authenticate...")
1915
- self.authenticate(revalidate=True)
1916
- request_header = self.request_header()
1917
- retries += 1
1918
- elif response.status_code in [502, 503, 504] and retries < 3:
1919
- logger.warning(
1920
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1921
- response.status_code,
1922
- (retries + 1) * 60,
1923
- )
1924
- time.sleep((retries + 1) * 60)
1925
- retries += 1
1926
- else:
1927
- logger.error(
1928
- "Failed to get M365 Team -> %s; status -> %s; error -> %s",
1929
- name,
1930
- response.status_code,
1931
- response.text,
1932
- )
1933
- return None
1684
+ return self.do_request(
1685
+ url=request_url,
1686
+ method="GET",
1687
+ headers=request_header,
1688
+ timeout=REQUEST_TIMEOUT,
1689
+ failure_message="Failed to get M365 Team -> '{}'".format(name),
1690
+ )
1934
1691
 
1935
1692
  # end method definition
1936
1693
 
@@ -1972,41 +1729,17 @@ class M365(object):
1972
1729
  request_url = self.config()["teamsUrl"]
1973
1730
  request_header = self.request_header()
1974
1731
 
1975
- logger.debug("Adding M365 Team -> %s; calling -> %s", name, request_url)
1976
- logger.debug("M365 Team attributes -> %s", team_post_body)
1732
+ logger.debug("Adding M365 Team -> '%s'; calling -> %s", name, request_url)
1733
+ logger.debug("M365 Team attributes -> %s", str(team_post_body))
1977
1734
 
1978
- retries = 0
1979
- while True:
1980
- response = requests.post(
1981
- request_url,
1982
- data=json.dumps(team_post_body),
1983
- headers=request_header,
1984
- timeout=REQUEST_TIMEOUT,
1985
- )
1986
- if response.ok:
1987
- return self.parse_request_response(response)
1988
- # Check if Session has expired - then re-authenticate and try once more
1989
- elif response.status_code == 401 and retries == 0:
1990
- logger.debug("Session has expired - try to re-authenticate...")
1991
- self.authenticate(revalidate=True)
1992
- request_header = self.request_header()
1993
- retries += 1
1994
- elif response.status_code in [502, 503, 504] and retries < 3:
1995
- logger.warning(
1996
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
1997
- response.status_code,
1998
- (retries + 1) * 60,
1999
- )
2000
- time.sleep((retries + 1) * 60)
2001
- retries += 1
2002
- else:
2003
- logger.error(
2004
- "Failed to add M365 Team -> '%s'; status -> %s; error -> %s",
2005
- name,
2006
- response.status_code,
2007
- response.text,
2008
- )
2009
- return None
1735
+ return self.do_request(
1736
+ url=request_url,
1737
+ method="POST",
1738
+ data=json.dumps(team_post_body),
1739
+ headers=request_header,
1740
+ timeout=REQUEST_TIMEOUT,
1741
+ failure_message="Failed to add M365 Team -> '{}'".format(name),
1742
+ )
2010
1743
 
2011
1744
  # end method definition
2012
1745
 
@@ -2029,30 +1762,13 @@ class M365(object):
2029
1762
  request_url,
2030
1763
  )
2031
1764
 
2032
- retries = 0
2033
- while True:
2034
- response = requests.delete(
2035
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
2036
- )
2037
- if response.ok:
2038
- return self.parse_request_response(response)
2039
- # Check if Session has expired - then re-authenticate and try once more
2040
- elif response.status_code == 401 and retries == 0:
2041
- logger.debug("Session has expired - try to re-authenticate...")
2042
- self.authenticate(revalidate=True)
2043
- request_header = self.request_header()
2044
- retries += 1
2045
- elif response.status_code in [502, 503, 504] and retries < 3:
2046
- logger.warning(
2047
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
2048
- response.status_code,
2049
- (retries + 1) * 60,
2050
- )
2051
- time.sleep((retries + 1) * 60)
2052
- retries += 1
2053
- else:
2054
- logger.error("Failed to delete M365 Team with ID -> %s", team_id)
2055
- return None
1765
+ return self.do_request(
1766
+ url=request_url,
1767
+ method="DELETE",
1768
+ headers=request_header,
1769
+ timeout=REQUEST_TIMEOUT,
1770
+ failure_message="Failed to delete M365 Team with ID -> {}".format(team_id),
1771
+ )
2056
1772
 
2057
1773
  # end method definition
2058
1774
 
@@ -2083,36 +1799,13 @@ class M365(object):
2083
1799
  request_url,
2084
1800
  )
2085
1801
 
2086
- retries = 0
2087
- while True:
2088
- response = requests.get(
2089
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
2090
- )
2091
- if response.ok:
2092
- existing_teams = self.parse_request_response(response)
2093
- break
2094
- # Check if Session has expired - then re-authenticate and try once more
2095
- elif response.status_code == 401 and retries == 0:
2096
- logger.debug("Session has expired - try to re-authenticate...")
2097
- self.authenticate(revalidate=True)
2098
- request_header = self.request_header()
2099
- retries += 1
2100
- elif response.status_code in [502, 503, 504] and retries < 3:
2101
- logger.warning(
2102
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
2103
- response.status_code,
2104
- (retries + 1) * 60,
2105
- )
2106
- time.sleep((retries + 1) * 60)
2107
- retries += 1
2108
- else:
2109
- logger.error(
2110
- "Failed to get list of M365 Teams to delete; status -> %s; error -> %s",
2111
- response.status_code,
2112
- response.text,
2113
- )
2114
- existing_teams = None
2115
- break
1802
+ existing_teams = self.do_request(
1803
+ url=request_url,
1804
+ method="GET",
1805
+ headers=request_header,
1806
+ timeout=REQUEST_TIMEOUT,
1807
+ failure_message="Failed to get list of M365 Teams to delete",
1808
+ )
2116
1809
 
2117
1810
  if existing_teams:
2118
1811
  data = existing_teams.get("value")
@@ -2124,7 +1817,7 @@ class M365(object):
2124
1817
 
2125
1818
  if not response:
2126
1819
  logger.error(
2127
- "Failed to delete M365 Team -> %s (%s)", name, team_id
1820
+ "Failed to delete M365 Team -> '%s' (%s)", name, team_id
2128
1821
  )
2129
1822
  continue
2130
1823
  counter += 1
@@ -2243,35 +1936,15 @@ class M365(object):
2243
1936
  request_url,
2244
1937
  )
2245
1938
 
2246
- retries = 0
2247
- while True:
2248
- response = requests.get(
2249
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
2250
- )
2251
- if response.ok:
2252
- return self.parse_request_response(response)
2253
- # Check if Session has expired - then re-authenticate and try once more
2254
- elif response.status_code == 401 and retries == 0:
2255
- logger.debug("Session has expired - try to re-authenticate...")
2256
- self.authenticate(revalidate=True)
2257
- request_header = self.request_header()
2258
- retries += 1
2259
- elif response.status_code in [502, 503, 504] and retries < 3:
2260
- logger.warning(
2261
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
2262
- response.status_code,
2263
- (retries + 1) * 60,
2264
- )
2265
- time.sleep((retries + 1) * 60)
2266
- retries += 1
2267
- else:
2268
- logger.error(
2269
- "Failed to get Channels for M365 Team -> %s; status -> %s; error -> %s",
2270
- name,
2271
- response.status_code,
2272
- response.text,
2273
- )
2274
- return None
1939
+ return self.do_request(
1940
+ url=request_url,
1941
+ method="GET",
1942
+ headers=request_header,
1943
+ timeout=REQUEST_TIMEOUT,
1944
+ failure_message="Failed to get Channels for M365 Team -> '{}' ({})".format(
1945
+ name, team_id
1946
+ ),
1947
+ )
2275
1948
 
2276
1949
  # end method definition
2277
1950
 
@@ -2346,38 +2019,15 @@ class M365(object):
2346
2019
  request_url,
2347
2020
  )
2348
2021
 
2349
- retries = 0
2350
- while True:
2351
- response = requests.get(
2352
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
2353
- )
2354
- if response.ok:
2355
- return self.parse_request_response(response)
2356
- # Check if Session has expired - then re-authenticate and try once more
2357
- elif response.status_code == 401 and retries == 0:
2358
- logger.debug("Session has expired - try to re-authenticate...")
2359
- self.authenticate(revalidate=True)
2360
- request_header = self.request_header()
2361
- retries += 1
2362
- elif response.status_code in [502, 503, 504] and retries < 3:
2363
- logger.warning(
2364
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
2365
- response.status_code,
2366
- (retries + 1) * 60,
2367
- )
2368
- time.sleep((retries + 1) * 60)
2369
- retries += 1
2370
- else:
2371
- logger.error(
2372
- "Failed to get Tabs for M365 Team -> %s (%s) and Channel -> %s (%s); status -> %s; error -> %s",
2373
- team_name,
2374
- team_id,
2375
- channel_name,
2376
- channel_id,
2377
- response.status_code,
2378
- response.text,
2379
- )
2380
- return None
2022
+ return self.do_request(
2023
+ url=request_url,
2024
+ method="GET",
2025
+ headers=request_header,
2026
+ timeout=REQUEST_TIMEOUT,
2027
+ failure_message="Failed to get Tabs for M365 Team -> '{}' ({}) and Channel -> '{}' ({})".format(
2028
+ team_name, team_id, channel_name, channel_id
2029
+ ),
2030
+ )
2381
2031
 
2382
2032
  # end method definition
2383
2033
 
@@ -2435,39 +2085,24 @@ class M365(object):
2435
2085
  filter_expression,
2436
2086
  request_url,
2437
2087
  )
2088
+ failure_message = (
2089
+ "Failed to get list of M365 Teams apps using filter -> {}".format(
2090
+ filter_expression
2091
+ )
2092
+ )
2438
2093
  else:
2439
2094
  logger.debug("Get list of all MS Teams Apps; calling -> %s", request_url)
2095
+ failure_message = "Failed to get list of M365 Teams apps"
2440
2096
 
2441
2097
  request_header = self.request_header()
2442
2098
 
2443
- retries = 0
2444
- while True:
2445
- response = requests.get(
2446
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
2447
- )
2448
- if response.ok:
2449
- return self.parse_request_response(response)
2450
- # Check if Session has expired - then re-authenticate and try once more
2451
- elif response.status_code == 401 and retries == 0:
2452
- logger.debug("Session has expired - try to re-authenticate...")
2453
- self.authenticate(revalidate=True)
2454
- request_header = self.request_header()
2455
- retries += 1
2456
- elif response.status_code in [502, 503, 504] and retries < 3:
2457
- logger.warning(
2458
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
2459
- response.status_code,
2460
- (retries + 1) * 60,
2461
- )
2462
- time.sleep((retries + 1) * 60)
2463
- retries += 1
2464
- else:
2465
- logger.error(
2466
- "Failed to get list of M365 Teams apps; status -> %s; error -> %s",
2467
- response.status_code,
2468
- response.text,
2469
- )
2470
- return None
2099
+ return self.do_request(
2100
+ url=request_url,
2101
+ method="GET",
2102
+ headers=request_header,
2103
+ timeout=REQUEST_TIMEOUT,
2104
+ failure_message=failure_message,
2105
+ )
2471
2106
 
2472
2107
  # end method definition
2473
2108
 
@@ -2514,34 +2149,13 @@ class M365(object):
2514
2149
 
2515
2150
  request_header = self.request_header()
2516
2151
 
2517
- retries = 0
2518
- while True:
2519
- response = requests.get(
2520
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
2521
- )
2522
- if response.ok:
2523
- return self.parse_request_response(response)
2524
- # Check if Session has expired - then re-authenticate and try once more
2525
- elif response.status_code == 401 and retries == 0:
2526
- logger.debug("Session has expired - try to re-authenticate...")
2527
- self.authenticate(revalidate=True)
2528
- request_header = self.request_header()
2529
- retries += 1
2530
- elif response.status_code in [502, 503, 504] and retries < 3:
2531
- logger.warning(
2532
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
2533
- response.status_code,
2534
- (retries + 1) * 60,
2535
- )
2536
- time.sleep((retries + 1) * 60)
2537
- retries += 1
2538
- else:
2539
- logger.error(
2540
- "Failed to get list of M365 Teams apps; status -> %s; error -> %s",
2541
- response.status_code,
2542
- response.text,
2543
- )
2544
- return None
2152
+ return self.do_request(
2153
+ url=request_url,
2154
+ method="GET",
2155
+ headers=request_header,
2156
+ timeout=REQUEST_TIMEOUT,
2157
+ failure_message="Failed to get M365 Teams app with ID -> {}".format(app_id),
2158
+ )
2545
2159
 
2546
2160
  # end method definition
2547
2161
 
@@ -2579,35 +2193,15 @@ class M365(object):
2579
2193
 
2580
2194
  request_header = self.request_header()
2581
2195
 
2582
- retries = 0
2583
- while True:
2584
- response = requests.get(
2585
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
2586
- )
2587
- if response.ok:
2588
- return self.parse_request_response(response)
2589
- # Check if Session has expired - then re-authenticate and try once more
2590
- elif response.status_code == 401 and retries == 0:
2591
- logger.debug("Session has expired - try to re-authenticate...")
2592
- self.authenticate(revalidate=True)
2593
- request_header = self.request_header()
2594
- retries += 1
2595
- elif response.status_code in [502, 503, 504] and retries < 3:
2596
- logger.warning(
2597
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
2598
- response.status_code,
2599
- (retries + 1) * 60,
2600
- )
2601
- time.sleep((retries + 1) * 60)
2602
- retries += 1
2603
- else:
2604
- logger.error(
2605
- "Failed to get list of M365 Teams Apps for user -> %s; status -> %s; error -> %s",
2606
- user_id,
2607
- response.status_code,
2608
- response.text,
2609
- )
2610
- return None
2196
+ return self.do_request(
2197
+ url=request_url,
2198
+ method="GET",
2199
+ headers=request_header,
2200
+ timeout=REQUEST_TIMEOUT,
2201
+ failure_message="Failed to get M365 Teams apps for user -> {}".format(
2202
+ user_id
2203
+ ),
2204
+ )
2611
2205
 
2612
2206
  # end method definition
2613
2207
 
@@ -2645,35 +2239,15 @@ class M365(object):
2645
2239
 
2646
2240
  request_header = self.request_header()
2647
2241
 
2648
- retries = 0
2649
- while True:
2650
- response = requests.get(
2651
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
2652
- )
2653
- if response.ok:
2654
- return self.parse_request_response(response)
2655
- # Check if Session has expired - then re-authenticate and try once more
2656
- elif response.status_code == 401 and retries == 0:
2657
- logger.debug("Session has expired - try to re-authenticate...")
2658
- self.authenticate(revalidate=True)
2659
- request_header = self.request_header()
2660
- retries += 1
2661
- elif response.status_code in [502, 503, 504] and retries < 3:
2662
- logger.warning(
2663
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
2664
- response.status_code,
2665
- (retries + 1) * 60,
2666
- )
2667
- time.sleep((retries + 1) * 60)
2668
- retries += 1
2669
- else:
2670
- logger.error(
2671
- "Failed to get list of M365 Teams Apps for M365 Team -> %s; status -> %s; error -> %s",
2672
- team_id,
2673
- response.status_code,
2674
- response.text,
2675
- )
2676
- return None
2242
+ return self.do_request(
2243
+ url=request_url,
2244
+ method="GET",
2245
+ headers=request_header,
2246
+ timeout=REQUEST_TIMEOUT,
2247
+ failure_message="Failed to get list of M365 Teams apps for M365 Team -> {}".format(
2248
+ team_id
2249
+ ),
2250
+ )
2677
2251
 
2678
2252
  # end method definition
2679
2253
 
@@ -2770,7 +2344,7 @@ class M365(object):
2770
2344
 
2771
2345
  # Here we need the credentials of an authenticated user!
2772
2346
  # (not the application credentials (client_id, client_secret))
2773
- request_header = self.request_header_user("application/zip")
2347
+ request_header = self.request_header_user(content_type="application/zip")
2774
2348
 
2775
2349
  with open(app_path, "rb") as f:
2776
2350
  app_data = f.read()
@@ -2790,48 +2364,16 @@ class M365(object):
2790
2364
  request_url,
2791
2365
  )
2792
2366
 
2793
- retries = 0
2794
- while True:
2795
- response = requests.post(
2796
- request_url,
2797
- headers=request_header,
2798
- data=app_data,
2799
- timeout=REQUEST_TIMEOUT,
2800
- )
2801
- if response.ok:
2802
- return self.parse_request_response(response)
2803
-
2804
- # Check if Session has expired - then re-authenticate and try once more
2805
- if response.status_code == 401 and retries == 0:
2806
- logger.debug("Session has expired - try to re-authenticate...")
2807
- self.authenticate(revalidate=True)
2808
- request_header = self.request_header()
2809
- retries += 1
2810
- elif response.status_code in [502, 503, 504] and retries < 3:
2811
- logger.warning(
2812
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
2813
- response.status_code,
2814
- (retries + 1) * 60,
2815
- )
2816
- time.sleep((retries + 1) * 60)
2817
- retries += 1
2818
- else:
2819
- if update_existing_app:
2820
- logger.warning(
2821
- "Failed to update existing M365 Teams app -> '%s' (may be because it is not a new version); status -> %s; error -> %s",
2822
- app_path,
2823
- response.status_code,
2824
- response.text,
2825
- )
2826
-
2827
- else:
2828
- logger.error(
2829
- "Failed to upload new M365 Teams app -> '%s'; status -> %s; error -> %s",
2830
- app_path,
2831
- response.status_code,
2832
- response.text,
2833
- )
2834
- return None
2367
+ return self.do_request(
2368
+ url=request_url,
2369
+ method="POST",
2370
+ headers=request_header,
2371
+ data=app_data,
2372
+ timeout=REQUEST_TIMEOUT,
2373
+ failure_message="Failed to update existing M365 Teams app -> '{}' (may be because it is not a new version)".format(
2374
+ app_path
2375
+ ),
2376
+ )
2835
2377
 
2836
2378
  # end method definition
2837
2379
 
@@ -2920,56 +2462,26 @@ class M365(object):
2920
2462
 
2921
2463
  logger.debug(
2922
2464
  "Assign M365 Teams app -> '%s' (%s) to M365 user -> %s; calling -> %s",
2923
- app_name,
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
2465
+ app_name,
2466
+ app_internal_id,
2467
+ user_id,
2468
+ request_url,
2469
+ )
2470
+
2471
+ return self.do_request(
2472
+ url=request_url,
2473
+ method="POST",
2474
+ headers=request_header,
2475
+ json_data=post_body,
2476
+ timeout=REQUEST_TIMEOUT,
2477
+ failure_message="Failed to assign M365 Teams app -> '{}' ({}) to M365 user -> {}".format(
2478
+ app_name, app_internal_id, user_id
2479
+ ),
2480
+ warning_message="Failed to assign M365 Teams app -> '{}' ({}) to M365 user -> {} (could be because the app is assigned organization-wide)".format(
2481
+ app_name, app_internal_id, user_id
2482
+ ),
2483
+ show_error=show_error,
2484
+ )
2973
2485
 
2974
2486
  # end method definition
2975
2487
 
@@ -3025,37 +2537,15 @@ class M365(object):
3025
2537
  request_url,
3026
2538
  )
3027
2539
 
3028
- retries = 0
3029
- while True:
3030
- response = requests.post(
3031
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
3032
- )
3033
- if response.ok:
3034
- return self.parse_request_response(response)
3035
- # Check if Session has expired - then re-authenticate and try once more
3036
- elif response.status_code == 401 and retries == 0:
3037
- logger.debug("Session has expired - try to re-authenticate...")
3038
- self.authenticate(revalidate=True)
3039
- request_header = self.request_header()
3040
- retries += 1
3041
- elif response.status_code in [502, 503, 504] and retries < 3:
3042
- logger.warning(
3043
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3044
- response.status_code,
3045
- (retries + 1) * 60,
3046
- )
3047
- time.sleep((retries + 1) * 60)
3048
- retries += 1
3049
- else:
3050
- logger.error(
3051
- "Failed to upgrade M365 Teams app -> '%s' (%s) of M365 user -> %s; status -> %s; error -> %s",
3052
- app_name,
3053
- app_installation_id,
3054
- user_id,
3055
- response.status_code,
3056
- response.text,
3057
- )
3058
- return None
2540
+ return self.do_request(
2541
+ url=request_url,
2542
+ method="POST",
2543
+ headers=request_header,
2544
+ timeout=REQUEST_TIMEOUT,
2545
+ failure_message="Failed to upgrade M365 Teams app -> '{}' ({}) of M365 user -> {}".format(
2546
+ app_name, app_installation_id, user_id
2547
+ ),
2548
+ )
3059
2549
 
3060
2550
  # end method definition
3061
2551
 
@@ -3107,37 +2597,15 @@ class M365(object):
3107
2597
  request_url,
3108
2598
  )
3109
2599
 
3110
- retries = 0
3111
- while True:
3112
- response = requests.delete(
3113
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
3114
- )
3115
- if response.ok:
3116
- return self.parse_request_response(response)
3117
- # Check if Session has expired - then re-authenticate and try once more
3118
- elif response.status_code == 401 and retries == 0:
3119
- logger.debug("Session has expired - try to re-authenticate...")
3120
- self.authenticate(revalidate=True)
3121
- request_header = self.request_header()
3122
- retries += 1
3123
- elif response.status_code in [502, 503, 504] and retries < 3:
3124
- logger.warning(
3125
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3126
- response.status_code,
3127
- (retries + 1) * 60,
3128
- )
3129
- time.sleep((retries + 1) * 60)
3130
- retries += 1
3131
- else:
3132
- logger.error(
3133
- "Failed to remove M365 Teams app -> '%s' (%s) from M365 user -> %s; status -> %s; error -> %s",
3134
- app_name,
3135
- app_installation_id,
3136
- user_id,
3137
- response.status_code,
3138
- response.text,
3139
- )
3140
- return None
2600
+ return self.do_request(
2601
+ url=request_url,
2602
+ method="DELETE",
2603
+ headers=request_header,
2604
+ timeout=REQUEST_TIMEOUT,
2605
+ failure_message="Failed to remove M365 Teams app -> '{}' ({}) from M365 user -> {}".format(
2606
+ app_name, app_installation_id, user_id
2607
+ ),
2608
+ )
3141
2609
 
3142
2610
  # end method definition
3143
2611
 
@@ -3168,40 +2636,16 @@ class M365(object):
3168
2636
  request_url,
3169
2637
  )
3170
2638
 
3171
- retries = 0
3172
- while True:
3173
- response = requests.post(
3174
- request_url,
3175
- json=post_body,
3176
- headers=request_header,
3177
- timeout=REQUEST_TIMEOUT,
3178
- )
3179
- if response.ok:
3180
- return self.parse_request_response(response)
3181
- # Check if Session has expired - then re-authenticate and try once more
3182
- elif response.status_code == 401 and retries == 0:
3183
- logger.debug("Session has expired - try to re-authenticate...")
3184
- self.authenticate(revalidate=True)
3185
- request_header = self.request_header()
3186
- retries += 1
3187
- elif response.status_code in [502, 503, 504] and retries < 3:
3188
- logger.warning(
3189
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3190
- response.status_code,
3191
- (retries + 1) * 60,
3192
- )
3193
- time.sleep((retries + 1) * 60)
3194
- retries += 1
3195
- else:
3196
- logger.error(
3197
- "Failed to assign M365 Teams app -> '%s' (%s) to M365 Team -> %s; status -> %s; error -> %s",
3198
- self.config()["teamsAppName"],
3199
- app_id,
3200
- team_id,
3201
- response.status_code,
3202
- response.text,
3203
- )
3204
- return None
2639
+ return self.do_request(
2640
+ url=request_url,
2641
+ method="POST",
2642
+ headers=request_header,
2643
+ json_data=post_body,
2644
+ timeout=REQUEST_TIMEOUT,
2645
+ failure_message="Failed to assign M365 Teams app -> '{}' ({}) to M365 Team -> {}".format(
2646
+ self.config()["teamsAppName"], app_id, team_id
2647
+ ),
2648
+ )
3205
2649
 
3206
2650
  # end method definition
3207
2651
 
@@ -3249,37 +2693,15 @@ class M365(object):
3249
2693
  request_url,
3250
2694
  )
3251
2695
 
3252
- retries = 0
3253
- while True:
3254
- response = requests.post(
3255
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
3256
- )
3257
- if response.ok:
3258
- return self.parse_request_response(response)
3259
- # Check if Session has expired - then re-authenticate and try once more
3260
- elif response.status_code == 401 and retries == 0:
3261
- logger.debug("Session has expired - try to re-authenticate...")
3262
- self.authenticate(revalidate=True)
3263
- request_header = self.request_header()
3264
- retries += 1
3265
- elif response.status_code in [502, 503, 504] and retries < 3:
3266
- logger.warning(
3267
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3268
- response.status_code,
3269
- (retries + 1) * 60,
3270
- )
3271
- time.sleep((retries + 1) * 60)
3272
- retries += 1
3273
- else:
3274
- logger.error(
3275
- "Failed to upgrade M365 Teams app -> '%s' (%s) of M365 team with ID -> %s; status -> %s; error -> %s",
3276
- app_name,
3277
- app_installation_id,
3278
- team_id,
3279
- response.status_code,
3280
- response.text,
3281
- )
3282
- return None
2696
+ return self.do_request(
2697
+ url=request_url,
2698
+ method="POST",
2699
+ headers=request_header,
2700
+ timeout=REQUEST_TIMEOUT,
2701
+ failure_message="Failed to upgrade M365 Teams app -> '{}' ({}) of M365 team with ID -> {}".format(
2702
+ app_name, app_installation_id, team_id
2703
+ ),
2704
+ )
3283
2705
 
3284
2706
  # end method definition
3285
2707
 
@@ -3364,42 +2786,16 @@ class M365(object):
3364
2786
  request_url,
3365
2787
  )
3366
2788
 
3367
- retries = 0
3368
- while True:
3369
- response = requests.post(
3370
- request_url,
3371
- headers=request_header,
3372
- json=tab_config,
3373
- timeout=REQUEST_TIMEOUT,
3374
- )
3375
- if response.ok:
3376
- return self.parse_request_response(response)
3377
- # Check if Session has expired - then re-authenticate and try once more
3378
- elif response.status_code == 401 and retries == 0:
3379
- logger.debug("Session has expired - try to re-authenticate...")
3380
- self.authenticate(revalidate=True)
3381
- request_header = self.request_header()
3382
- retries += 1
3383
- elif response.status_code in [502, 503, 504] and retries < 3:
3384
- logger.warning(
3385
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3386
- response.status_code,
3387
- (retries + 1) * 60,
3388
- )
3389
- time.sleep((retries + 1) * 60)
3390
- retries += 1
3391
- else:
3392
- logger.error(
3393
- "Failed to add Tab for M365 Team -> '%s' (%s) and Channel -> '%s' (%s); status -> %s; error -> %s; tab config -> %s",
3394
- team_name,
3395
- team_id,
3396
- channel_name,
3397
- channel_id,
3398
- response.status_code,
3399
- response.text,
3400
- str(tab_config),
3401
- )
3402
- return None
2789
+ return self.do_request(
2790
+ url=request_url,
2791
+ method="POST",
2792
+ headers=request_header,
2793
+ json_data=tab_config,
2794
+ timeout=REQUEST_TIMEOUT,
2795
+ failure_message="Failed to add Tab for M365 Team -> '{}' ({}) and Channel -> '{}' ({})".format(
2796
+ team_name, team_id, channel_name, channel_id
2797
+ ),
2798
+ )
3403
2799
 
3404
2800
  # end method definition
3405
2801
 
@@ -3506,43 +2902,16 @@ class M365(object):
3506
2902
  request_url,
3507
2903
  )
3508
2904
 
3509
- retries = 0
3510
- while True:
3511
- response = requests.patch(
3512
- request_url,
3513
- headers=request_header,
3514
- json=tab_config,
3515
- timeout=REQUEST_TIMEOUT,
3516
- )
3517
- if response.ok:
3518
- return self.parse_request_response(response)
3519
- # Check if Session has expired - then re-authenticate and try once more
3520
- elif response.status_code == 401 and retries == 0:
3521
- logger.debug("Session has expired - try to re-authenticate...")
3522
- self.authenticate(revalidate=True)
3523
- request_header = self.request_header()
3524
- retries += 1
3525
- elif response.status_code in [502, 503, 504] and retries < 3:
3526
- logger.warning(
3527
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3528
- response.status_code,
3529
- (retries + 1) * 60,
3530
- )
3531
- time.sleep((retries + 1) * 60)
3532
- retries += 1
3533
- else:
3534
- logger.error(
3535
- "Failed to update Tab -> '%s' (%s) for M365 Team -> '%s' (%s) and Channel -> '%s' (%s); status -> %s; error -> %s",
3536
- tab_name,
3537
- tab_id,
3538
- team_name,
3539
- team_id,
3540
- channel_name,
3541
- channel_id,
3542
- response.status_code,
3543
- response.text,
3544
- )
3545
- return None
2905
+ return self.do_request(
2906
+ url=request_url,
2907
+ method="PATCH",
2908
+ headers=request_header,
2909
+ json_data=tab_config,
2910
+ timeout=REQUEST_TIMEOUT,
2911
+ failure_message="Failed to update Tab -> '{}' ({}) for M365 Team -> '{}' ({}) and Channel -> '{}' ({})".format(
2912
+ tab_name, tab_id, team_name, team_id, channel_name, channel_id
2913
+ ),
2914
+ )
3546
2915
 
3547
2916
  # end method definition
3548
2917
 
@@ -3633,49 +3002,23 @@ class M365(object):
3633
3002
  request_url,
3634
3003
  )
3635
3004
 
3636
- retries = 0
3637
- while True:
3638
- response = requests.delete(
3639
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
3640
- )
3641
- if response.ok:
3642
- logger.debug(
3643
- "Tab -> '%s' (%s) has been deleted from Channel -> '%s' (%s) of Microsoft 365 Teams -> '%s' (%s)",
3644
- tab_name,
3645
- tab_id,
3646
- channel_name,
3647
- channel_id,
3648
- team_name,
3649
- team_id,
3650
- )
3651
- break
3652
- # Check if Session has expired - then re-authenticate and try once more
3653
- elif response.status_code == 401 and retries == 0:
3654
- logger.debug("Session has expired - try to re-authenticate...")
3655
- self.authenticate(revalidate=True)
3656
- request_header = self.request_header()
3657
- retries += 1
3658
- elif response.status_code in [502, 503, 504] and retries < 3:
3659
- logger.warning(
3660
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3661
- response.status_code,
3662
- (retries + 1) * 60,
3663
- )
3664
- time.sleep((retries + 1) * 60)
3665
- retries += 1
3666
- else:
3667
- logger.error(
3668
- "Failed to delete Tab -> '%s' (%s) for M365 Team -> '%s' (%s) and Channel -> '%s' (%s); status -> %s; error -> %s",
3669
- tab_name,
3670
- tab_id,
3671
- team_name,
3672
- team_id,
3673
- channel_name,
3674
- channel_id,
3675
- response.status_code,
3676
- response.text,
3677
- )
3678
- return False
3005
+ response = self.do_request(
3006
+ url=request_url,
3007
+ method="DELETE",
3008
+ headers=request_header,
3009
+ timeout=REQUEST_TIMEOUT,
3010
+ failure_message="Failed to delete Tab -> '{}' ({}) for M365 Team -> '{}' ({}) and Channel -> '{}' ({})".format(
3011
+ tab_name, tab_id, team_name, team_id, channel_name, channel_id
3012
+ ),
3013
+ parse_request_response=False,
3014
+ )
3015
+
3016
+ if response and response.ok:
3017
+ break
3018
+ else:
3019
+ return False
3020
+ # end for tab in tab_list
3021
+
3679
3022
  return True
3680
3023
 
3681
3024
  # end method definition
@@ -3778,36 +3121,16 @@ class M365(object):
3778
3121
  request_url,
3779
3122
  )
3780
3123
 
3781
- retries = 0
3782
- while True:
3783
- response = requests.post(
3784
- request_url, headers=request_header, json=body, timeout=REQUEST_TIMEOUT
3785
- )
3786
- if response.ok:
3787
- return self.parse_request_response(response)
3788
- # Check if Session has expired - then re-authenticate and try once more
3789
- elif response.status_code == 401 and retries == 0:
3790
- logger.debug("Session has expired - try to re-authenticate...")
3791
- self.authenticate(revalidate=True)
3792
- request_header = self.request_header()
3793
- retries += 1
3794
- elif response.status_code in [502, 503, 504] and retries < 3:
3795
- logger.warning(
3796
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3797
- response.status_code,
3798
- (retries + 1) * 60,
3799
- )
3800
- time.sleep((retries + 1) * 60)
3801
- retries += 1
3802
- else:
3803
- logger.error(
3804
- "Failed to assign label -> '%s' to M365 user -> '%s'; status -> %s; error -> %s",
3805
- label_name,
3806
- user_email,
3807
- response.status_code,
3808
- response.text,
3809
- )
3810
- return None
3124
+ return self.do_request(
3125
+ url=request_url,
3126
+ method="POST",
3127
+ headers=request_header,
3128
+ json_data=body,
3129
+ timeout=REQUEST_TIMEOUT,
3130
+ failure_message="Failed to assign label -> '{}' to M365 user -> '{}'".format(
3131
+ label_name, user_email
3132
+ ),
3133
+ )
3811
3134
 
3812
3135
  # end method definition
3813
3136
 
@@ -3832,7 +3155,7 @@ class M365(object):
3832
3155
 
3833
3156
  # request_header = self.request_header()
3834
3157
 
3835
- logger.debug("Install Outlook Add-in from '%s' (NOT IMPLEMENTED)", app_path)
3158
+ logger.debug("Install Outlook Add-in from -> '%s' (NOT IMPLEMENTED)", app_path)
3836
3159
 
3837
3160
  response = None
3838
3161
 
@@ -3864,35 +3187,15 @@ class M365(object):
3864
3187
  request_url,
3865
3188
  )
3866
3189
 
3867
- retries = 0
3868
- while True:
3869
- response = requests.get(
3870
- request_url, headers=request_header, timeout=REQUEST_TIMEOUT
3871
- )
3872
- if response.ok:
3873
- return self.parse_request_response(response)
3874
- # Check if Session has expired - then re-authenticate and try once more
3875
- elif response.status_code == 401 and retries == 0:
3876
- logger.debug("Session has expired - try to re-authenticate...")
3877
- self.authenticate(revalidate=True)
3878
- request_header = self.request_header()
3879
- retries += 1
3880
- elif response.status_code in [502, 503, 504] and retries < 3:
3881
- logger.warning(
3882
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3883
- response.status_code,
3884
- (retries + 1) * 60,
3885
- )
3886
- time.sleep((retries + 1) * 60)
3887
- retries += 1
3888
- else:
3889
- logger.error(
3890
- "Cannot find Azure App Registration -> '%s'; status -> %s; error -> %s",
3891
- app_registration_name,
3892
- response.status_code,
3893
- response.text,
3894
- )
3895
- return None
3190
+ return self.do_request(
3191
+ url=request_url,
3192
+ method="GET",
3193
+ headers=request_header,
3194
+ timeout=REQUEST_TIMEOUT,
3195
+ failure_message="Cannot find Azure App Registration -> '{}'".format(
3196
+ app_registration_name
3197
+ ),
3198
+ )
3896
3199
 
3897
3200
  # end method definition
3898
3201
 
@@ -3963,38 +3266,16 @@ class M365(object):
3963
3266
  request_url = self.config()["applicationsUrl"]
3964
3267
  request_header = self.request_header()
3965
3268
 
3966
- retries = 0
3967
- while True:
3968
- response = requests.post(
3969
- request_url,
3970
- headers=request_header,
3971
- json=app_registration_data,
3972
- timeout=REQUEST_TIMEOUT,
3973
- )
3974
- if response.ok:
3975
- return self.parse_request_response(response)
3976
- # Check if Session has expired - then re-authenticate and try once more
3977
- elif response.status_code == 401 and retries == 0:
3978
- logger.debug("Session has expired - try to re-authenticate...")
3979
- self.authenticate(revalidate=True)
3980
- request_header = self.request_header()
3981
- retries += 1
3982
- elif response.status_code in [502, 503, 504] and retries < 3:
3983
- logger.warning(
3984
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
3985
- response.status_code,
3986
- (retries + 1) * 60,
3987
- )
3988
- time.sleep((retries + 1) * 60)
3989
- retries += 1
3990
- else:
3991
- logger.error(
3992
- "Cannot add App Registration -> '%s'; status -> %s; error -> %s",
3993
- app_registration_name,
3994
- response.status_code,
3995
- response.text,
3996
- )
3997
- return None
3269
+ return self.do_request(
3270
+ url=request_url,
3271
+ method="POST",
3272
+ headers=request_header,
3273
+ json_data=app_registration_data,
3274
+ timeout=REQUEST_TIMEOUT,
3275
+ failure_message="Cannot add App Registration -> '{}'".format(
3276
+ app_registration_name
3277
+ ),
3278
+ )
3998
3279
 
3999
3280
  # end method definition
4000
3281
 
@@ -4035,39 +3316,16 @@ class M365(object):
4035
3316
  request_url,
4036
3317
  )
4037
3318
 
4038
- retries = 0
4039
- while True:
4040
- response = requests.patch(
4041
- request_url,
4042
- headers=request_header,
4043
- json=app_registration_data,
4044
- timeout=REQUEST_TIMEOUT,
4045
- )
4046
- if response.ok:
4047
- return self.parse_request_response(response)
4048
- # Check if Session has expired - then re-authenticate and try once more
4049
- elif response.status_code == 401 and retries == 0:
4050
- logger.debug("Session has expired - try to re-authenticate...")
4051
- self.authenticate(revalidate=True)
4052
- request_header = self.request_header()
4053
- retries += 1
4054
- elif response.status_code in [502, 503, 504] and retries < 3:
4055
- logger.warning(
4056
- "M365 Graph API delivered server side error -> %s; retrying in %s seconds...",
4057
- response.status_code,
4058
- (retries + 1) * 60,
4059
- )
4060
- time.sleep((retries + 1) * 60)
4061
- retries += 1
4062
- else:
4063
- logger.error(
4064
- "Cannot update App Registration -> '%s' (%s); status -> %s; error -> %s",
4065
- app_registration_name,
4066
- app_registration_id,
4067
- response.status_code,
4068
- response.text,
4069
- )
4070
- return None
3319
+ return self.do_request(
3320
+ url=request_url,
3321
+ method="PATCH",
3322
+ headers=request_header,
3323
+ json_data=app_registration_data,
3324
+ timeout=REQUEST_TIMEOUT,
3325
+ failure_message="Cannot update App Registration -> '{}' ({})".format(
3326
+ app_registration_name, app_registration_id
3327
+ ),
3328
+ )
4071
3329
 
4072
3330
  # end method definition
4073
3331
 
@@ -4109,67 +3367,40 @@ class M365(object):
4109
3367
  request_header = self.request_header()
4110
3368
 
4111
3369
  logger.debug(
4112
- "Retrieve mails for user -> %s from -> '%s' with subject -> '%s'; calling -> %s",
3370
+ "Get mails for user -> %s from -> '%s' with subject -> '%s'; calling -> %s",
4113
3371
  user_id,
4114
3372
  sender,
4115
3373
  subject,
4116
3374
  request_url,
4117
3375
  )
4118
3376
 
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
3377
+ response = self.do_request(
3378
+ url=request_url,
3379
+ method="GET",
3380
+ headers=request_header,
3381
+ timeout=REQUEST_TIMEOUT,
3382
+ failure_message="Cannot retrieve emails for user -> {}".format(user_id),
3383
+ show_error=show_error,
3384
+ )
3385
+
3386
+ if response and "value" in response:
3387
+ messages = response["value"]
3388
+
3389
+ # Filter the messages by sender and subject in code
3390
+ filtered_messages = [
3391
+ msg
3392
+ for msg in messages
3393
+ if msg.get("from", {}).get("emailAddress", {}).get("address") == sender
3394
+ and subject in msg.get("subject", "")
3395
+ ]
3396
+ response["value"] = filtered_messages
3397
+ return response
3398
+
3399
+ return None
4169
3400
 
4170
3401
  # end method definition
4171
3402
 
4172
- def get_mail_body(self, user_id: str, email_id: str) -> str:
3403
+ def get_mail_body(self, user_id: str, email_id: str) -> str | None:
4173
3404
  """Get full email body for a given email ID
4174
3405
  This requires Mail.Read Application permissions for the Azure App being used.
4175
3406
 
@@ -4177,7 +3408,7 @@ class M365(object):
4177
3408
  user_id (str): M365 ID of the user
4178
3409
  email_id (str): M365 ID of the email
4179
3410
  Returns:
4180
- str: Email body or None of the request fails.
3411
+ str | None: Email body or None of the request fails.
4181
3412
  """
4182
3413
 
4183
3414
  request_url = (
@@ -4191,36 +3422,22 @@ class M365(object):
4191
3422
 
4192
3423
  request_header = self.request_header()
4193
3424
 
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
3425
+ response = self.do_request(
3426
+ url=request_url,
3427
+ method="GET",
3428
+ headers=request_header,
3429
+ timeout=REQUEST_TIMEOUT,
3430
+ failure_message="Cannot get email body for user -> {} and email with ID -> {}".format(
3431
+ user_id,
3432
+ email_id,
3433
+ ),
3434
+ parse_request_response=False,
3435
+ )
3436
+
3437
+ if response and response.ok and response.content:
3438
+ return response.content.decode("utf-8")
3439
+
3440
+ return None
4224
3441
 
4225
3442
  # end method definition
4226
3443
 
@@ -4310,36 +3527,15 @@ class M365(object):
4310
3527
 
4311
3528
  request_header = self.request_header()
4312
3529
 
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
3530
+ return self.do_request(
3531
+ url=request_url,
3532
+ method="DELETE",
3533
+ headers=request_header,
3534
+ timeout=REQUEST_TIMEOUT,
3535
+ failure_message="Cannot delete email with ID -> {} from inbox of user -> {}".format(
3536
+ email_id, user_id
3537
+ ),
3538
+ )
4343
3539
 
4344
3540
  # end method definition
4345
3541
 
@@ -4521,8 +3717,8 @@ class M365(object):
4521
3717
  password_submit_xpath,
4522
3718
  )
4523
3719
  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!
3720
+ # The Terms of service dialog has some weird animation
3721
+ # which require a short wait time. It seems it is required!
4526
3722
  time.sleep(1)
4527
3723
  terms_accept_button = browser_automation_object.find_elem(
4528
3724
  find_elem=terms_of_service_xpath,