microsoft-graph-helpers 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- microsoft_graph_helpers/__init__.py +30 -0
- microsoft_graph_helpers/api_token.py +92 -0
- microsoft_graph_helpers/core.py +79 -0
- microsoft_graph_helpers/email.py +88 -0
- microsoft_graph_helpers/groups.py +40 -0
- microsoft_graph_helpers/security.py +26 -0
- microsoft_graph_helpers/users.py +85 -0
- microsoft_graph_helpers-0.1.0.dist-info/METADATA +16 -0
- microsoft_graph_helpers-0.1.0.dist-info/RECORD +11 -0
- microsoft_graph_helpers-0.1.0.dist-info/WHEEL +5 -0
- microsoft_graph_helpers-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .api_token import (
|
|
2
|
+
get_bearer_token,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
from .core import (
|
|
6
|
+
get_headers,
|
|
7
|
+
make_graph_api_request,
|
|
8
|
+
handle_graph_api_error
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .email import (
|
|
12
|
+
send_message_as,
|
|
13
|
+
retrieve_message
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .groups import (
|
|
17
|
+
get_group_guid,
|
|
18
|
+
get_group_members,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from .security import (
|
|
22
|
+
run_hunting_query,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from .users import (
|
|
26
|
+
verify_user_exists,
|
|
27
|
+
revoke_ms_sessions,
|
|
28
|
+
reset_ms_password,
|
|
29
|
+
get_user_direct_group_memberships,
|
|
30
|
+
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import requests
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
# In-memory token cache
|
|
8
|
+
_token_cache = {}
|
|
9
|
+
|
|
10
|
+
def _generate_cache_key(tenant_id, client_id, secret, scope):
|
|
11
|
+
key_string = f"{tenant_id}:{client_id}:{secret}:{scope}"
|
|
12
|
+
return hashlib.sha256(key_string.encode()).hexdigest()
|
|
13
|
+
|
|
14
|
+
def get_bearer_token(
|
|
15
|
+
tenant_id: str,
|
|
16
|
+
client_id: str,
|
|
17
|
+
client_secret: str,
|
|
18
|
+
scope: str = "https://graph.microsoft.com/.default"
|
|
19
|
+
) -> str:
|
|
20
|
+
"""
|
|
21
|
+
Fetches a bearer token from Microsoft Identity Platform using client credentials.
|
|
22
|
+
This function uses an in-memory cache to avoid redundant token requests.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
tenant_id (str): Azure AD tenant ID.
|
|
26
|
+
client_id (str): Application (client) ID.
|
|
27
|
+
client_secret (str): Application secret.
|
|
28
|
+
scope (str, optional): OAuth2 scope. Defaults to Microsoft Graph.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
str: Access token string.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
TokenRequestError: If the token request fails or returns a non-200 response.
|
|
35
|
+
RuntimeError: If the response is not valid JSON or if the request times out.
|
|
36
|
+
"""
|
|
37
|
+
logging.debug(f"get_bearer_token called for tenant_id={tenant_id[:5]}..., client_id={client_id[:5]}...")
|
|
38
|
+
cache_key = _generate_cache_key(tenant_id, client_id, client_secret, scope)
|
|
39
|
+
cached = _token_cache.get(cache_key)
|
|
40
|
+
|
|
41
|
+
if cached and cached['expires_at'] > time.time():
|
|
42
|
+
logging.debug(f"Retrieved cached Microsoft Graph token: {cache_key}")
|
|
43
|
+
return cached['token']
|
|
44
|
+
|
|
45
|
+
logging.debug(f"No valid cached Microsoft Graph token found for key: {cache_key}")
|
|
46
|
+
token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
|
|
47
|
+
data = {
|
|
48
|
+
'client_id': client_id,
|
|
49
|
+
'client_secret': client_secret,
|
|
50
|
+
'scope': scope,
|
|
51
|
+
'grant_type': 'client_credentials'
|
|
52
|
+
}
|
|
53
|
+
headers = {
|
|
54
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
response = requests.post(token_url, data=data, headers=headers, timeout=10)
|
|
59
|
+
try:
|
|
60
|
+
response_json = response.json()
|
|
61
|
+
except ValueError:
|
|
62
|
+
logging.critical("Non-JSON response received when retrieving Microsoft Graph token.")
|
|
63
|
+
raise RuntimeError("Invalid JSON response received when retrieving Microsoft Graph token.")
|
|
64
|
+
|
|
65
|
+
if response.status_code == 200:
|
|
66
|
+
token = response_json.get('access_token')
|
|
67
|
+
if not token:
|
|
68
|
+
logging.critical("Microsoft Graph token not found in the response.")
|
|
69
|
+
raise RuntimeError("Microsoft Graph token not found in the response.")
|
|
70
|
+
|
|
71
|
+
expires_in = response_json.get('expires_in', 3599)
|
|
72
|
+
_token_cache[cache_key] = {
|
|
73
|
+
'token': token,
|
|
74
|
+
'expires_at': time.time() + expires_in - 60
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
logging.debug(
|
|
78
|
+
f"Obtained Microsoft Graph bearer token with expiry at {time.ctime(_token_cache[cache_key]['expires_at'])}."
|
|
79
|
+
)
|
|
80
|
+
return token
|
|
81
|
+
|
|
82
|
+
error_dump = dict({'http_status_code': response.status_code}, **response_json)
|
|
83
|
+
logging.critical(json.dumps(error_dump))
|
|
84
|
+
raise TokenRequestError(json.dumps(error_dump, indent=4))
|
|
85
|
+
|
|
86
|
+
except requests.exceptions.Timeout:
|
|
87
|
+
logging.critical("Microsoft Graph token request timed out.")
|
|
88
|
+
raise RuntimeError("Microsoft Graph token request timed out.")
|
|
89
|
+
|
|
90
|
+
class TokenRequestError(Exception):
|
|
91
|
+
"""Raised when a Microsoft Graph token request fails."""
|
|
92
|
+
pass
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import json, logging, requests, time
|
|
2
|
+
from typing import List, Union
|
|
3
|
+
from requests.exceptions import Timeout, RequestException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_headers(bearer_token):
|
|
7
|
+
# returns basic request header for making Graph calls
|
|
8
|
+
return {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
'Authorization': 'Bearer ' + bearer_token
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def make_graph_api_request(
|
|
14
|
+
method: str,
|
|
15
|
+
url: str,
|
|
16
|
+
headers: dict,
|
|
17
|
+
payload: Union[dict, None] = None,
|
|
18
|
+
context: str = "",
|
|
19
|
+
retries: int = 3,
|
|
20
|
+
timeout: int = 15
|
|
21
|
+
) -> Union[dict, bool]:
|
|
22
|
+
for attempt in range(1, retries + 1):
|
|
23
|
+
try:
|
|
24
|
+
if method.upper() == "GET":
|
|
25
|
+
response = requests.get(url, headers=headers, timeout=timeout)
|
|
26
|
+
elif method.upper() == "POST":
|
|
27
|
+
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
|
|
28
|
+
elif method.upper() == "PATCH":
|
|
29
|
+
response = requests.patch(url, headers=headers, json=payload, timeout=timeout)
|
|
30
|
+
else:
|
|
31
|
+
logging.error(f"[{context}] Unsupported HTTP method: {method}")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
if response.status_code in (200, 202, 204):
|
|
35
|
+
if response.status_code == 204:
|
|
36
|
+
logging.debug(f"[{context}] Http Status Code: {response.status_code} - Request submitted successfully.")
|
|
37
|
+
return True
|
|
38
|
+
elif response.status_code == 202:
|
|
39
|
+
logging.debug(f"[{context}] Http Status Code: {response.status_code} - Request submitted successfully.")
|
|
40
|
+
return True
|
|
41
|
+
try:
|
|
42
|
+
return response.json()
|
|
43
|
+
except ValueError:
|
|
44
|
+
logging.warning(f"[{context}] Http Status Code: {response.status_code} - Response returned no JSON content.")
|
|
45
|
+
return True
|
|
46
|
+
else:
|
|
47
|
+
handle_graph_api_error(response, context=context)
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
except Timeout:
|
|
51
|
+
logging.warning(f"[{context}] Timeout on attempt {attempt}/{retries}. Retrying...")
|
|
52
|
+
except RequestException as e:
|
|
53
|
+
logging.error(f"[{context}] RequestException: {e}")
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
time.sleep(2 ** attempt)
|
|
57
|
+
|
|
58
|
+
logging.error(f"[{context}] All retry attempts failed.")
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def handle_graph_api_error(response: requests.Response, context: str = "") -> None:
|
|
62
|
+
"""
|
|
63
|
+
Handles and logs errors from Microsoft Graph API responses.
|
|
64
|
+
"""
|
|
65
|
+
status_code = response.status_code
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
result = response.json()
|
|
69
|
+
error = result.get("error", {})
|
|
70
|
+
code = error.get("code", "UnknownError")
|
|
71
|
+
message = error.get("message", "No error message provided.")
|
|
72
|
+
except (ValueError, AttributeError) as e:
|
|
73
|
+
code = "InvalidResponse"
|
|
74
|
+
message = f"Failed to parse error response: {e}"
|
|
75
|
+
logging.error(f"Raw response: {response.text}")
|
|
76
|
+
|
|
77
|
+
prefix = f"[{context}] " if context else ""
|
|
78
|
+
logging.warning(f"{prefix}Http Status Code: {status_code} - {code}.")
|
|
79
|
+
logging.warning(f"{prefix}{message}")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import json, logging, requests, urllib.parse
|
|
2
|
+
from typing import List, Tuple, Union
|
|
3
|
+
from .core import get_headers, make_graph_api_request
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def send_message_as(
|
|
7
|
+
bearer_token: str,
|
|
8
|
+
sender_email: str,
|
|
9
|
+
recipient_email: str,
|
|
10
|
+
subject: str,
|
|
11
|
+
body: str,
|
|
12
|
+
save_to_sent_messages: bool = False,
|
|
13
|
+
) -> bool:
|
|
14
|
+
logging.debug(f"Sending message from {sender_email} to {recipient_email}")
|
|
15
|
+
headers = get_headers(bearer_token)
|
|
16
|
+
payload = {
|
|
17
|
+
"message": {
|
|
18
|
+
"subject": subject,
|
|
19
|
+
"body": {
|
|
20
|
+
"contentType": "Text",
|
|
21
|
+
"content": body
|
|
22
|
+
},
|
|
23
|
+
"toRecipients": [
|
|
24
|
+
{
|
|
25
|
+
"emailAddress": {
|
|
26
|
+
"address": recipient_email
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"saveToSentItems": str(save_to_sent_messages).lower()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
response = make_graph_api_request(
|
|
35
|
+
method="POST",
|
|
36
|
+
url=f"https://graph.microsoft.com/v1.0/users/{sender_email}/sendMail",
|
|
37
|
+
headers=headers,
|
|
38
|
+
payload=payload,
|
|
39
|
+
timeout=10,
|
|
40
|
+
context="send_message_as",
|
|
41
|
+
)
|
|
42
|
+
if response == True:
|
|
43
|
+
logging.debug(f"[send_message_as] Message sent to {recipient_email}")
|
|
44
|
+
else:
|
|
45
|
+
logging.debug(f"[send_message_as] Failed to send message to {recipient_email}")
|
|
46
|
+
return response
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def retrieve_message(
|
|
50
|
+
bearer_token: str,
|
|
51
|
+
user_principal_name: str,
|
|
52
|
+
internet_message_id: str
|
|
53
|
+
) -> Tuple[Union[dict, None], Union[str, None]]:
|
|
54
|
+
"""
|
|
55
|
+
Searches for a message by its Internet Message ID across multiple folders
|
|
56
|
+
in the user's mailbox.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Tuple[dict or None, str or None]: The message object and the folder name it was found in.
|
|
60
|
+
"""
|
|
61
|
+
logging.debug(f"Searching for message ID \"{internet_message_id}\" in mailbox \"{user_principal_name}\"")
|
|
62
|
+
encoded_id = urllib.parse.quote(internet_message_id)
|
|
63
|
+
headers = get_headers(bearer_token)
|
|
64
|
+
folders = [
|
|
65
|
+
("Messages", f"https://graph.microsoft.com/v1.0/users/{user_principal_name}/messages?$filter=internetMessageId eq '{encoded_id}'"),
|
|
66
|
+
("DeletedItems", f"https://graph.microsoft.com/v1.0/users/{user_principal_name}/mailFolders/DeletedItems/messages?$filter=internetMessageId eq '{encoded_id}'"),
|
|
67
|
+
("RecoverableItemsDeletions", f"https://graph.microsoft.com/v1.0/users/{user_principal_name}/mailFolders/RecoverableItemsDeletions/messages?$filter=internetMessageId eq '{encoded_id}'"),
|
|
68
|
+
("RecoverableItemsPurges", f"https://graph.microsoft.com/v1.0/users/{user_principal_name}/mailFolders/RecoverableItemsPurges/messages?$filter=internetMessageId eq '{encoded_id}'"),
|
|
69
|
+
("RecoverableItemsDiscoveryHolds", f"https://graph.microsoft.com/v1.0/users/{user_principal_name}/mailFolders/RecoverableItemsDiscoveryHolds/messages?$filter=internetMessageId eq '{encoded_id}'")
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
for folder_name, url in folders:
|
|
73
|
+
logging.debug(f"Checking folder: {folder_name}")
|
|
74
|
+
response = make_graph_api_request(
|
|
75
|
+
method="GET",
|
|
76
|
+
url=url,
|
|
77
|
+
headers=headers,
|
|
78
|
+
timeout=10,
|
|
79
|
+
context=f"retrieve_message:{folder_name}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if isinstance(response, dict) and response.get("value"):
|
|
83
|
+
logging.info(f"[retrieve_message] Message found in folder: {folder_name}")
|
|
84
|
+
return response, folder_name
|
|
85
|
+
|
|
86
|
+
logging.warning(f"Message ID \"{internet_message_id}\" not found in any folder for user \"{user_principal_name}\".")
|
|
87
|
+
return None, None
|
|
88
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import json, logging, requests
|
|
2
|
+
from .core import get_headers, make_graph_api_request
|
|
3
|
+
|
|
4
|
+
def get_group_guid(bearer_token: str, group_name: str):
|
|
5
|
+
logging.debug(f"Obtaining GUID for group named {group_name} from Microsoft Graph API.")
|
|
6
|
+
|
|
7
|
+
url = f"https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '{group_name}'"
|
|
8
|
+
headers = get_headers(bearer_token)
|
|
9
|
+
response = make_graph_api_request(
|
|
10
|
+
method="GET",
|
|
11
|
+
url=url,
|
|
12
|
+
headers=headers,
|
|
13
|
+
context="get_group_guid"
|
|
14
|
+
)
|
|
15
|
+
if isinstance(response, dict):
|
|
16
|
+
value = response.get("value", [])
|
|
17
|
+
if len(value) > 1:
|
|
18
|
+
logging.warning(f"Multiple groups found with name '{group_name}'. Returning the first match.")
|
|
19
|
+
if value and isinstance(value, list):
|
|
20
|
+
return value[0].get("id")
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_group_members(bearer_token: str, guid: str):
|
|
25
|
+
logging.debug(f"Obtaining members of group whose GUID is {guid} from Microsoft Graph API.")
|
|
26
|
+
|
|
27
|
+
url = f"https://graph.microsoft.com/v1.0/groups/{guid}/members"
|
|
28
|
+
headers = get_headers(bearer_token)
|
|
29
|
+
|
|
30
|
+
response = make_graph_api_request(
|
|
31
|
+
method="GET",
|
|
32
|
+
url=url,
|
|
33
|
+
headers=headers,
|
|
34
|
+
context="get_group_members"
|
|
35
|
+
)
|
|
36
|
+
if isinstance(response, dict) and 'value' in response:
|
|
37
|
+
logging.debug(f"Response from Microsoft Graph API contained {len(response['value'])} members.")
|
|
38
|
+
return [member.get('mail') for member in response['value'] if member.get('mail')]
|
|
39
|
+
return False
|
|
40
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import json, logging, requests
|
|
2
|
+
from typing import List, Union
|
|
3
|
+
from .core import get_headers, make_graph_api_request
|
|
4
|
+
|
|
5
|
+
def run_hunting_query(bearer_token: str, query: str, timespan: str = "P90D") -> Union[List[dict], bool]:
|
|
6
|
+
logging.debug("Submitting Hunting Query as POST request to Graph API.")
|
|
7
|
+
|
|
8
|
+
url = "https://graph.microsoft.com/v1.0/security/runHuntingQuery"
|
|
9
|
+
headers = get_headers(bearer_token)
|
|
10
|
+
payload = {
|
|
11
|
+
"Query": query,
|
|
12
|
+
"Timespan": timespan
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
response = make_graph_api_request(
|
|
16
|
+
method="POST",
|
|
17
|
+
url=url,
|
|
18
|
+
headers=headers,
|
|
19
|
+
payload=payload,
|
|
20
|
+
context="run_hunting_query"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if isinstance(response, dict) and "results" in response:
|
|
24
|
+
logging.debug("200: Hunting query received successful response")
|
|
25
|
+
return response["results"]
|
|
26
|
+
return False
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import json, logging, requests
|
|
2
|
+
from .core import get_headers, make_graph_api_request
|
|
3
|
+
|
|
4
|
+
def verify_user_exists(bearer_token, user_principal_name):
|
|
5
|
+
logging.debug(f"Querying Microsoft Graph for user {user_principal_name}")
|
|
6
|
+
url = f"https://graph.microsoft.com/v1.0/users/{user_principal_name}"
|
|
7
|
+
headers = get_headers(bearer_token)
|
|
8
|
+
|
|
9
|
+
response = make_graph_api_request(
|
|
10
|
+
method="GET",
|
|
11
|
+
url=url,
|
|
12
|
+
headers=headers,
|
|
13
|
+
context="verify_user_exists"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if response is False:
|
|
17
|
+
logging.debug(f"[verify_user_exists] User {user_principal_name} not found.")
|
|
18
|
+
return False
|
|
19
|
+
else:
|
|
20
|
+
logging.debug(f"[verify_user_exists] User {user_principal_name} exists.")
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def revoke_ms_sessions(bearer_token, user_principal_name):
|
|
25
|
+
logging.debug(f"[revoke_ms_sessions] Revoking sign-in sessions for {user_principal_name}")
|
|
26
|
+
|
|
27
|
+
url = f"https://graph.microsoft.com/v1.0/users/{user_principal_name}/revokeSignInSessions"
|
|
28
|
+
headers = get_headers(bearer_token)
|
|
29
|
+
response = make_graph_api_request(
|
|
30
|
+
method="POST",
|
|
31
|
+
url=url,
|
|
32
|
+
headers=headers,
|
|
33
|
+
context="revoke_ms_sessions"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if response is False:
|
|
37
|
+
logging.debug(f"[revoke_ms_sessions] for {user_principal_name} failed.")
|
|
38
|
+
return False
|
|
39
|
+
logging.debug(f"[revoke_ms_sessions] for {user_principal_name} succeeded.")
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def reset_ms_password(bearer_token, user_principal_name, password):
|
|
44
|
+
logging.debug(f"[reset_ms_password] Resetting password for {user_principal_name}.")
|
|
45
|
+
|
|
46
|
+
url = f"https://graph.microsoft.com/v1.0/users/{user_principal_name}"
|
|
47
|
+
headers = get_headers(bearer_token)
|
|
48
|
+
headers.update({'Accept': "application/json"})
|
|
49
|
+
payload = {
|
|
50
|
+
'passwordProfile': {
|
|
51
|
+
'forceChangePasswordNextSignIn': False,
|
|
52
|
+
'password': password
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
response = make_graph_api_request(
|
|
57
|
+
method="PATCH",
|
|
58
|
+
url=url,
|
|
59
|
+
headers=headers,
|
|
60
|
+
payload=payload,
|
|
61
|
+
context="reset_ms_password"
|
|
62
|
+
)
|
|
63
|
+
if response is False:
|
|
64
|
+
logging.debug(f"reset_ms_password] Failed to reset password for {user_principal_name}.")
|
|
65
|
+
return False
|
|
66
|
+
logging.debug(f"[reset_ms_password] Reset password for {user_principal_name}.")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_user_direct_group_memberships(bearer_token, user_principal_name):
|
|
71
|
+
logging.debug(f"[get_user_direct_group_memberships] Fetching group memberships for {user_principal_name}.")
|
|
72
|
+
|
|
73
|
+
url = f"https://graph.microsoft.com/v1.0/users/{user_principal_name}/memberOf"
|
|
74
|
+
headers = get_headers(bearer_token)
|
|
75
|
+
response = make_graph_api_request(
|
|
76
|
+
method="GET",
|
|
77
|
+
url=url,
|
|
78
|
+
headers=headers,
|
|
79
|
+
context="get_user_direct_group_memberships"
|
|
80
|
+
)
|
|
81
|
+
if response is False:
|
|
82
|
+
logging.debug(f"Failed to fetch group memberships for {user_principal_name}.")
|
|
83
|
+
return False
|
|
84
|
+
logging.debug(f"Succeeded in fetching group memberships for {user_principal_name}.")
|
|
85
|
+
return response
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: microsoft_graph_helpers
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Helper functions for interacting with Microsoft Graph API
|
|
5
|
+
Author: Lucas Krupinski
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: requests>=2.32.5
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
|
|
12
|
+
# Microsoft Graph Helpers
|
|
13
|
+
|
|
14
|
+
A Python module for calling various Microsoft Graph endpoints. Not nearly as comprehensive as Microsoft's official modules, but far lighter weight and with a minimal amount of dependencies.
|
|
15
|
+
|
|
16
|
+
Not comprehensive, only contains calls that I've needed to user in my other applets.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
microsoft_graph_helpers/__init__.py,sha256=S07qPxI-j7FQVKVjvSGPO6QFNcSWueEein1xwyXtmS0,462
|
|
2
|
+
microsoft_graph_helpers/api_token.py,sha256=5B_GeOeIdI-D2IVwUpdiPwV5-ldG8aV22tx4EMrl7So,3459
|
|
3
|
+
microsoft_graph_helpers/core.py,sha256=O_jyONwqkGDvqeKrNEOaWF-g8da7YHuJ1NcHOv2Hsko,3075
|
|
4
|
+
microsoft_graph_helpers/email.py,sha256=Bn1vtJVlgdsVNrRSap4BO3yQLnRyUvTjRCtKoVWRHM0,3512
|
|
5
|
+
microsoft_graph_helpers/groups.py,sha256=7fVGKsMhrCy86HUMGTcVgGP-nS41vQYoxQhQkOMWDK4,1497
|
|
6
|
+
microsoft_graph_helpers/security.py,sha256=ah4-ivoclOz2vRH4whAh5n-AZdqamwO8vouANRRQVW8,843
|
|
7
|
+
microsoft_graph_helpers/users.py,sha256=rYYplRNKdIcZfpFjotoQ8AGR297e-RWDvSj3xvg4NPA,2997
|
|
8
|
+
microsoft_graph_helpers-0.1.0.dist-info/METADATA,sha256=7bZAbuXCAI1ZD1c42BujBae3xvISfBqWlepFehXqLa0,596
|
|
9
|
+
microsoft_graph_helpers-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
microsoft_graph_helpers-0.1.0.dist-info/top_level.txt,sha256=A4584PIoEOJgkpiRByl6xTiRGNVE1onTOvKgHQbkMY8,24
|
|
11
|
+
microsoft_graph_helpers-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
microsoft_graph_helpers
|