diff --git a/setup.cfg b/setup.cfg index 0c17a98f..ae3bd149 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = IntuneCD -version = 1.3.5 +version = 1.4.0 author = Tobias Almén author_email = almenscorner@outlook.com description = Tool to backup and update configurations in Intune diff --git a/src/IntuneCD/assignment_report.py b/src/IntuneCD/assignment_report.py new file mode 100644 index 00000000..aea09751 --- /dev/null +++ b/src/IntuneCD/assignment_report.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +""" +This module creates a report of all groups found and their assginment. +""" + +import os +import platform + +from .save_output import save_output +from .check_file import check_file +from .load_file import load_file + + +def get_group_report(path, output): + """ + This function is used to get a report of all groups and what they are assigned to. + + Args: + path (str): Path to save the report to + output (str): Output format for the report + """ + + report_path = f"{path}/Assignment Report/" + + def process_file(path, name, payload_type, groups): + file_check = check_file(path, name) + if file_check: + with open(os.path.join(path, name), "r") as f: + data = load_file(name, f) + if type(data) is dict and data.get("assignments"): + for assignment in data["assignments"]: + if assignment["target"].get("groupName"): + if data.get("displayName"): + name = data["displayName"] + elif data.get("name"): + name = data["name"] + data = { + "groupName": assignment["target"]["groupName"], + "groupType": assignment["target"].get("groupType"), + "membershipRule": assignment["target"].get( + "membershipRule", None + ), + "assignedTo": {}, + } + + payload_added = False # flag to track whether payload_type has been added + + if not groups: + groups.append(data) + data["assignedTo"][payload_type] = [name] + payload_added = True + else: + for item in groups: + if item["groupName"] == data["groupName"]: + if not payload_added and item["assignedTo"].get( + payload_type + ): + item["assignedTo"][payload_type].append( + name + ) + payload_added = True + elif not payload_added and not item[ + "assignedTo" + ].get(payload_type): + item["assignedTo"][payload_type] = [name] + payload_added = True + + if not payload_added: + data["assignedTo"][payload_type] = [name] + groups.append(data) + + def collect_groups(path): + groups = [] + slash = "/" + run_os = platform.uname().system + if run_os == "Windows": + slash = "\\" + abs_path = os.path.abspath(path) + for root, dirs, files in os.walk(path, topdown=True): + abs_root = os.path.abspath(root) + for file in files: + os.path.abspath(root) + payload_type = abs_root.replace(abs_path, "").split(slash) + if len(payload_type) > 1: + payload_type = payload_type[1] + process_file( + str(root), + file, + payload_type, + groups, + ) + return groups + + groups = collect_groups(path) + + if groups: + save_output(output, report_path, "report", groups) diff --git a/src/IntuneCD/backup_appConfiguration.py b/src/IntuneCD/backup_appConfiguration.py index bf936d69..3456aab8 100644 --- a/src/IntuneCD/backup_appConfiguration.py +++ b/src/IntuneCD/backup_appConfiguration.py @@ -11,7 +11,9 @@ from .remove_keys import remove_keys # Set MS Graph endpoint -ENDPOINT = "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations" +ENDPOINT = ( + "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations" +) APP_ENDPOINT = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps" @@ -31,8 +33,9 @@ def savebackup(path, output, exclude, token): data = makeapirequest(ENDPOINT, token) if data["value"]: - - assignment_responses = batch_assignment(data, "deviceAppManagement/mobileAppConfigurations/", "/assignments", token) + assignment_responses = batch_assignment( + data, "deviceAppManagement/mobileAppConfigurations/", "/assignments", token + ) for profile in data["value"]: config_count += 1 @@ -58,7 +61,9 @@ def savebackup(path, output, exclude, token): print("Backing up App Configuration: " + profile["displayName"]) # Get filename without illegal characters - fname = clean_filename(f"{profile['displayName']}_{str(profile['@odata.type'].split('.')[2])}") + fname = clean_filename( + f"{profile['displayName']}_{str(profile['@odata.type'].split('.')[2])}" + ) # Save App Configuration as JSON or YAML depending on configured value # in "-o" save_output(output, configpath, fname, profile) diff --git a/src/IntuneCD/backup_powershellScripts.py b/src/IntuneCD/backup_powershellScripts.py index 0f9b647b..092baed3 100644 --- a/src/IntuneCD/backup_powershellScripts.py +++ b/src/IntuneCD/backup_powershellScripts.py @@ -36,13 +36,19 @@ def savebackup(path, output, exclude, token): for script in data["value"]: script_ids.append(script["id"]) - assignment_responses = batch_assignment(data, "deviceManagement/intents/", "/assignments", token) - script_data_responses = batch_request(script_ids, "deviceManagement/deviceManagementScripts/", "", token) + assignment_responses = batch_assignment( + data, "deviceManagement/deviceManagementScripts/", "/assignments", token + ) + script_data_responses = batch_request( + script_ids, "deviceManagement/deviceManagementScripts/", "", token + ) for script_data in script_data_responses: config_count += 1 if "assignments" not in exclude: - assignments = get_object_assignment(script_data["id"], assignment_responses) + assignments = get_object_assignment( + script_data["id"], assignment_responses + ) if assignments: script_data["assignments"] = assignments diff --git a/src/IntuneCD/backup_shellScripts.py b/src/IntuneCD/backup_shellScripts.py index 9621bdb4..3beb37c8 100644 --- a/src/IntuneCD/backup_shellScripts.py +++ b/src/IntuneCD/backup_shellScripts.py @@ -15,7 +15,9 @@ # Set MS Graph endpoint ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/deviceShellScripts/" -ASSIGNMENT_ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts" +ASSIGNMENT_ENDPOINT = ( + "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts" +) # Get all Shell scripts and save them in specified path @@ -36,8 +38,12 @@ def savebackup(path, output, exclude, token): for script in data["value"]: script_ids.append(script["id"]) - assignment_responses = batch_assignment(data, "deviceManagement/deviceManagementScripts/", "/assignments", token) - script_data_responses = batch_request(script_ids, "deviceManagement/deviceShellScripts/", "", token) + assignment_responses = batch_assignment( + data, "deviceManagement/deviceShellScripts/", "?$expand=assignments", token + ) + script_data_responses = batch_request( + script_ids, "deviceManagement/deviceShellScripts/", "", token + ) for script_data in script_data_responses: config_count += 1 diff --git a/src/IntuneCD/graph_batch.py b/src/IntuneCD/graph_batch.py index c0978d82..5d6eeef6 100644 --- a/src/IntuneCD/graph_batch.py +++ b/src/IntuneCD/graph_batch.py @@ -39,7 +39,9 @@ def batch_request(data, url, extra_url, token, method="GET") -> list: # POST to the graph batch endpoint json_data = json.dumps(query_data) - request = makeapirequestPost("https://graph.microsoft.com/beta/$batch", token, jdata=json_data) + request = makeapirequestPost( + "https://graph.microsoft.com/beta/$batch", token, jdata=json_data + ) request_data = sorted(request["responses"], key=lambda item: item.get("id")) # Append each successful request to responses list @@ -69,9 +71,15 @@ def batch_assignment(data, url, extra_url, token, app_protection=False) -> list: # If getting App Protection Assignments, get the platform if app_protection is True: for id in data["value"]: - if id["@odata.type"] == "#microsoft.graph.mdmWindowsInformationProtectionPolicy": + if ( + id["@odata.type"] + == "#microsoft.graph.mdmWindowsInformationProtectionPolicy" + ): data_ids.append(f"mdmWindowsInformationProtectionPolicies/{id['id']}") - if id["@odata.type"] == "#microsoft.graph.windowsInformationProtectionPolicy": + if ( + id["@odata.type"] + == "#microsoft.graph.windowsInformationProtectionPolicy" + ): data_ids.append(f"windowsInformationProtectionPolicies/{id['id']}") else: data_ids.append(f"{str(id['@odata.type']).split('.')[2]}s/{id['id']}") @@ -83,6 +91,17 @@ def batch_assignment(data, url, extra_url, token, app_protection=False) -> list: if data_ids: responses = batch_request(data_ids, url, extra_url, token) if responses: + if extra_url == "?$expand=assignments": + response_values = [] + for value in responses: + response_values.append( + { + "value": value["assignments"], + "@odata.context": value["assignments@odata.context"], + } + ) + responses = response_values + group_ids = [ val for list in responses @@ -105,7 +124,12 @@ def batch_assignment(data, url, extra_url, token, app_protection=False) -> list: # Batch get name of the groups if group_ids: - group_responses = batch_request(group_ids, "groups/", "?$select=displayName,id", token) + group_responses = batch_request( + group_ids, + "groups/", + "?$select=displayName,id,groupTypes,membershipRule", + token, + ) for value in responses: if value["value"]: for val in value["value"]: @@ -113,17 +137,36 @@ def batch_assignment(data, url, extra_url, token, app_protection=False) -> list: for id in group_responses: if id["id"] == val["target"]["groupId"]: val["target"]["groupName"] = id["displayName"] + if "DynamicMembership" in id["groupTypes"]: + val["target"]["groupType"] = "DynamicMembership" + val["target"]["membershipRule"] = id[ + "membershipRule" + ] + else: + val["target"]["groupType"] = "StaticMembership" # Batch get name of the Filters if filter_ids: - filter_responses = batch_request(filter_ids, "deviceManagement/assignmentFilters/", "?$select=displayName", token) + filter_responses = batch_request( + filter_ids, + "deviceManagement/assignmentFilters/", + "?$select=displayName", + token, + ) for value in responses: if value["value"]: for val in value["value"]: if "deviceAndAppManagementAssignmentFilterId" in val["target"]: for id in filter_responses: - if id["id"] == val["target"]["deviceAndAppManagementAssignmentFilterId"]: - val["target"]["deviceAndAppManagementAssignmentFilterId"] = id["displayName"] + if ( + id["id"] + == val["target"][ + "deviceAndAppManagementAssignmentFilterId" + ] + ): + val["target"][ + "deviceAndAppManagementAssignmentFilterId" + ] = id["displayName"] return responses @@ -145,12 +188,19 @@ def batch_intents(data, token) -> dict: intent_values = {"value": []} # Get each template ID - filtered_data = [val for list in data["value"] for key, val in list.items() if "templateId" in key and val is not None] + filtered_data = [ + val + for list in data["value"] + for key, val in list.items() + if "templateId" in key and val is not None + ] template_ids = list(dict.fromkeys(filtered_data)) # Batch get all categories from templates if template_ids: - categories_responses = batch_request(template_ids, f"{base_url}/templates/", "/categories", token) + categories_responses = batch_request( + template_ids, f"{base_url}/templates/", "/categories", token + ) # Build ID for requesting settings for each Intent if categories_responses: @@ -158,7 +208,8 @@ def batch_intents(data, token) -> dict: settings_ids = [ val for list in categories_responses - if intent["templateId"] is not None and intent["templateId"] in list["@odata.context"] + if intent["templateId"] is not None + and intent["templateId"] in list["@odata.context"] for val in list["value"] for keys, val in val.items() if "id" in keys @@ -168,13 +219,18 @@ def batch_intents(data, token) -> dict: # Batch get all settings for all Intents if settings_id: - settings_responses = batch_request(settings_id, f"{base_url}/intents/", "/settings", token) + settings_responses = batch_request( + settings_id, f"{base_url}/intents/", "/settings", token + ) # If the Intent ID is in the responses, save the settings to settingsDelta for the Intent if settings_responses: for intent in data["value"]: settingsDelta = [ - val for list in settings_responses if intent["id"] in list["@odata.context"] for val in list["value"] + val + for list in settings_responses + if intent["id"] in list["@odata.context"] + for val in list["value"] ] intent_values["value"].append( { @@ -200,7 +256,12 @@ def get_object_assignment(id, responses) -> list: """ remove_keys = {"id", "groupId", "sourceId"} - assignments_list = [val for list in responses if id in list["@odata.context"] for val in list["value"]] + assignments_list = [ + val + for list in responses + if id in list["@odata.context"] + for val in list["value"] + ] for value in assignments_list: for k in remove_keys: value.pop(k, None) @@ -218,5 +279,10 @@ def get_object_details(id, responses) -> list: :return: List of details for the object """ - details = [val for list in responses if id in list["@odata.context"] for val in list["value"]] + details = [ + val + for list in responses + if id in list["@odata.context"] + for val in list["value"] + ] return details diff --git a/src/IntuneCD/run_backup.py b/src/IntuneCD/run_backup.py index 4da0794f..7eb03075 100644 --- a/src/IntuneCD/run_backup.py +++ b/src/IntuneCD/run_backup.py @@ -153,7 +153,6 @@ def selected_mode(argument): ) def run_backup(path, output, exclude, token): - config_count = 0 if "AppConfigurations" not in exclude: @@ -281,6 +280,10 @@ def run_backup(path, output, exclude, token): config_count += savebackup(path, output, token) + from .assignment_report import get_group_report + + get_group_report(path, output) + return config_count if args.output == "json" or args.output == "yaml": @@ -293,7 +296,6 @@ def run_backup(path, output, exclude, token): exclude = [] if args.frontend: - old_stdout = sys.stdout sys.stdout = feedstdout = StringIO() count = run_backup(args.path, args.output, exclude, token) @@ -307,6 +309,34 @@ def run_backup(path, output, exclude, token): body = {"type": "backup", "feed": out} update_frontend(f"{args.frontend}/api/feed/update", body) + body = [] + + from .load_file import load_file + from .check_file import check_file + + config_path = f"{args.path}/Assignment Report" + file_name = f"report.{args.output}" + if os.path.exists(config_path): + file_check = check_file(config_path, file_name) + if file_check: + with open(f"{config_path}/{file_name}", "r") as f: + assignment_summary = load_file(file_name, f) + if assignment_summary: + for assignment in assignment_summary: + body.append( + { + "groupName": assignment["groupName"], + "groupType": assignment["groupType"], + "membershipRule": assignment["membershipRule"], + "assignedTo": assignment["assignedTo"], + } + ) + + if len(body) > 0: + update_frontend( + f"{args.frontend}/api/assignments/summary", body + ) + else: run_backup(args.path, args.output, exclude, token) diff --git a/src/IntuneCD/run_update.py b/src/IntuneCD/run_update.py index 9614183c..84099f56 100644 --- a/src/IntuneCD/run_update.py +++ b/src/IntuneCD/run_update.py @@ -23,7 +23,6 @@ import sys import base64 import argparse -import json from io import StringIO from .get_authparams import getAuth @@ -33,7 +32,9 @@ def start(): - parser = argparse.ArgumentParser(description="Update Intune configurations with values from backup") + parser = argparse.ArgumentParser( + description="Update Intune configurations with values from backup" + ) parser.add_argument( "-p", "--path", @@ -115,6 +116,12 @@ def start(): help="When this parameter is set, no updates are pushed to Intune but the change summary is pushed to the frontend", action="store_true", ) + parser.add_argument( + "-g", + "--create-groups", + help="When this parameter is set, groups are created if they do not exist", + action="store_true", + ) args = parser.parse_args() @@ -143,25 +150,24 @@ def selected_mode(argument): tenant="PROD", ) - def run_update(path, token, assignment, exclude, report): - + def run_update(path, token, assignment, exclude, report, create_groups): diff_count = 0 diff_summary = [] if "AppConfigurations" not in exclude: from .update_appConfiguration import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "AppProtection" not in exclude: from .update_appProtection import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "Compliance" not in exclude: from .update_compliance import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "NotificationTemplate" not in exclude: from .update_notificationTemplate import update @@ -171,12 +177,12 @@ def run_update(path, token, assignment, exclude, report): if "Profiles" not in exclude: from .update_profiles import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "GPOConfigurations" not in exclude: from .update_groupPolicyConfiguration import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "AppleEnrollmentProfile" not in exclude: from .update_appleEnrollmentProfile import update @@ -186,17 +192,17 @@ def run_update(path, token, assignment, exclude, report): if "WindowsEnrollmentProfile" not in exclude: from .update_windowsEnrollmentProfile import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "EnrollmentStatusPage" not in exclude: from .update_enrollmentStatusPage import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "EnrollmentConfigurations" not in exclude: from .update_enrollmentConfigurations import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "Filters" not in exclude: from .update_assignmentFilter import update @@ -206,27 +212,27 @@ def run_update(path, token, assignment, exclude, report): if "Intents" not in exclude: from .update_managementIntents import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "ProactiveRemediation" not in exclude: from .update_proactiveRemediation import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "PowershellScripts" not in exclude: from .update_powershellScripts import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "ShellScripts" not in exclude: from .update_shellScripts import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "ConfigurationPolicies" not in exclude: from .update_configurationPolicies import update - diff_summary.append(update(path, token, assignment, report)) + diff_summary.append(update(path, token, assignment, report, create_groups)) if "ConditionalAccess" not in exclude: from .update_conditionalAccess import update @@ -242,7 +248,6 @@ def run_update(path, token, assignment, exclude, report): if token is None: raise Exception("Token is empty, please check os.environ variables") else: - if args.exclude: exclude = args.exclude else: @@ -252,7 +257,6 @@ def run_update(path, token, assignment, exclude, report): print("***Running in report mode, no updates will be pushed to Intune***") if args.frontend: - old_stdout = sys.stdout sys.stdout = feedstdout = StringIO() summary = run_update(args.path, token, args.u, exclude, args.report) @@ -282,7 +286,9 @@ def run_update(path, token, assignment, exclude, report): update_frontend(f"{args.frontend}/api/changes/summary", body) else: - run_update(args.path, token, args.u, exclude, args.report) + run_update( + args.path, token, args.u, exclude, args.report, args.create_groups + ) if __name__ == "__main__": diff --git a/src/IntuneCD/save_output.py b/src/IntuneCD/save_output.py index a43f1d6c..9dd2d283 100644 --- a/src/IntuneCD/save_output.py +++ b/src/IntuneCD/save_output.py @@ -27,7 +27,7 @@ def save_output(output, configpath, fname, data): yaml.dump(data, yamlFile, sort_keys=False, default_flow_style=False) elif output == "json": with open(configpath + fname + ".json", "w") as jsonFile: - json.dump(data, jsonFile, indent=10) + json.dump(data, jsonFile, indent=5) else: raise ValueError("Invalid output format") diff --git a/src/IntuneCD/update_appConfiguration.py b/src/IntuneCD/update_appConfiguration.py index a0201987..a5788c54 100644 --- a/src/IntuneCD/update_appConfiguration.py +++ b/src/IntuneCD/update_appConfiguration.py @@ -23,7 +23,7 @@ APP_ENDPOINT = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all App Configuration Polices in Intune, if the configuration in Intune differs from the JSON/YAML file. @@ -38,7 +38,6 @@ def update(path, token, assignment=False, report=False): configpath = path + "/" + "App Configuration/" # If App Configuration path exists, continue if os.path.exists(configpath): - # Get App Configurations mem_data = makeapirequest(ENDPOINT, token) # Get current assignments @@ -107,7 +106,9 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"assignments": update} post_assignment_update( @@ -156,7 +157,7 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = {"assignments": assignment} diff --git a/src/IntuneCD/update_appProtection.py b/src/IntuneCD/update_appProtection.py index 717905d3..ac3d1026 100644 --- a/src/IntuneCD/update_appProtection.py +++ b/src/IntuneCD/update_appProtection.py @@ -20,7 +20,7 @@ ENDPOINT = "https://graph.microsoft.com/beta/deviceAppManagement/" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all App Protection Polices in Intune, if the configuration in Intune differs from the JSON/YAML file. @@ -35,7 +35,6 @@ def update(path, token, assignment=False, report=False): diff_summary = [] # If App Configuration path exists, continue if os.path.exists(configpath): - # Get App Protections mem_data = makeapirequest(f"{ENDPOINT}managedAppPolicies", token) # Get current assignments @@ -123,7 +122,9 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"assignments": update} post_assignment_update( @@ -153,7 +154,7 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = {"assignments": assignment} diff --git a/src/IntuneCD/update_assignment.py b/src/IntuneCD/update_assignment.py index 5ddd3fa4..6137500c 100644 --- a/src/IntuneCD/update_assignment.py +++ b/src/IntuneCD/update_assignment.py @@ -5,6 +5,7 @@ """ import json +import uuid from deepdiff import DeepDiff from .graph_request import makeapirequest, makeapirequestPost @@ -69,7 +70,7 @@ def get_added_removed(diff_object) -> list: return update -def update_assignment(repo, mem, token) -> list: +def update_assignment(repo, mem, token, create_groups) -> list: """ This function is used to update assignments for configurations in Intune. @@ -98,7 +99,36 @@ def update_assignment(repo, mem, token) -> list: ) if request["value"]: val["target"].pop("groupName") + val["target"].pop("groupType", None) + val["target"].pop("membershipRule", None) val["target"]["groupId"] = request["value"][0]["id"] + else: + if create_groups: + group_data = { + "description": "Created by IntuneCD", + "displayName": val["target"]["groupName"], + "securityEnabled": True, + "mailEnabled": False, + "mailNickname": uuid.uuid4().hex, + } + if val["target"]["groupType"] == "DynamicMembership": + group_data["groupTypes"] = ["DynamicMembership"] + group_data["membershipRule"] = val["target"][ + "membershipRule" + ] + group_data["membershipRuleProcessingState"] = "On" + + request = makeapirequestPost( + "https://graph.microsoft.com/beta/groups", + token, + None, + json.dumps(group_data), + 201, + ) + val["target"].pop("groupName") + val["target"].pop("groupType", None) + val["target"].pop("membershipRule", None) + val["target"]["groupId"] = request["id"] # Request filter id based on filter name if val["target"]["deviceAndAppManagementAssignmentFilterId"]: diff --git a/src/IntuneCD/update_compliance.py b/src/IntuneCD/update_compliance.py index 7422336b..54b2edb5 100644 --- a/src/IntuneCD/update_compliance.py +++ b/src/IntuneCD/update_compliance.py @@ -21,7 +21,7 @@ ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/deviceCompliancePolicies" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Compliance Polices in Intune, if the configuration in Intune differs from the JSON/YAML file. @@ -158,7 +158,9 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"assignments": update} post_assignment_update( @@ -187,7 +189,7 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = {"assignments": assignment} diff --git a/src/IntuneCD/update_configurationPolicies.py b/src/IntuneCD/update_configurationPolicies.py index fd50c02b..d54f3bb5 100644 --- a/src/IntuneCD/update_configurationPolicies.py +++ b/src/IntuneCD/update_configurationPolicies.py @@ -19,7 +19,7 @@ ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Settings Catalog configurations in Intune, if the configuration in Intune differs from the JSON/YAML file. @@ -105,7 +105,9 @@ def update(path, token, assignment=False, report=False): mem_assign_obj = get_object_assignment( data["value"]["id"], mem_assignments ) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"assignments": update} post_assignment_update( @@ -135,7 +137,7 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = {"assignments": assignment} diff --git a/src/IntuneCD/update_enrollmentConfigurations.py b/src/IntuneCD/update_enrollmentConfigurations.py index 9f4bf012..a17cf586 100644 --- a/src/IntuneCD/update_enrollmentConfigurations.py +++ b/src/IntuneCD/update_enrollmentConfigurations.py @@ -23,7 +23,7 @@ ) -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """_summary_ Args: @@ -156,7 +156,9 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = { "enrollmentConfigurationAssignments": update @@ -216,7 +218,7 @@ def update(path, token, assignment=False, report=False): mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = { diff --git a/src/IntuneCD/update_enrollmentStatusPage.py b/src/IntuneCD/update_enrollmentStatusPage.py index 77e9128a..79c09eca 100644 --- a/src/IntuneCD/update_enrollmentStatusPage.py +++ b/src/IntuneCD/update_enrollmentStatusPage.py @@ -23,7 +23,7 @@ APP_ENDPOINT = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Windows Enrollment Status Page Profiles in Intune, if the configuration in Intune differs from the JSON/YAML file. @@ -122,7 +122,9 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: target = [{"target": t["target"]} for t in update] request_data = { @@ -155,7 +157,7 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: assignments = [] diff --git a/src/IntuneCD/update_groupPolicyConfiguration.py b/src/IntuneCD/update_groupPolicyConfiguration.py index df370bcc..dc226af0 100644 --- a/src/IntuneCD/update_groupPolicyConfiguration.py +++ b/src/IntuneCD/update_groupPolicyConfiguration.py @@ -344,7 +344,7 @@ def update_definition(repo_data, data, mem_id, mem_def_ids, token, report=False) return diff_summary -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Group Policy configurations in Intune, if the configuration in Intune differs from the JSON/YAML file. @@ -483,7 +483,9 @@ def update(path, token, assignment=False, report=False): # If assignments should be updated, look for updates and push them to Intune if assignment and report is False: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"assignments": update} post_assignment_update( @@ -544,7 +546,9 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] - assignment = update_assignment(assign_obj, mem_assign_obj, token) + assignment = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if assignment is not None: request_data = {"assignments": assignment} diff --git a/src/IntuneCD/update_managementIntents.py b/src/IntuneCD/update_managementIntents.py index ab88840b..a0fa36da 100644 --- a/src/IntuneCD/update_managementIntents.py +++ b/src/IntuneCD/update_managementIntents.py @@ -19,7 +19,7 @@ BASE_ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Endpoint Security configurations (intents) in Intune, if the configuration in Intune differs from the JSON/YAML file. @@ -45,7 +45,6 @@ def update(path, token, assignment=False, report=False): # Set glob pattern pattern = configpath + "*/*" for filename in glob.glob(pattern, recursive=True): - # If file is .DS_Store, skip if filename == ".DS_Store": continue @@ -70,7 +69,6 @@ def update(path, token, assignment=False, report=False): repo_data["displayName"] == intent["displayName"] and repo_data["templateId"] == intent["templateId"] ): - mem_data = intent # If Intent exists, continue @@ -141,7 +139,9 @@ def update(path, token, assignment=False, report=False): mem_assign_obj = get_object_assignment( mem_data["id"], mem_assignments ) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"assignments": update} post_assignment_update( @@ -174,7 +174,7 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = {"assignments": assignment} diff --git a/src/IntuneCD/update_powershellScripts.py b/src/IntuneCD/update_powershellScripts.py index 4ecfc3d8..d5f5c20c 100644 --- a/src/IntuneCD/update_powershellScripts.py +++ b/src/IntuneCD/update_powershellScripts.py @@ -21,7 +21,7 @@ ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Powershell scripts in Intune if, the configuration in Intune differs from the JSON/YAML file. @@ -134,7 +134,9 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"deviceManagementScriptAssignments": update} post_assignment_update( @@ -163,7 +165,7 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = { diff --git a/src/IntuneCD/update_proactiveRemediation.py b/src/IntuneCD/update_proactiveRemediation.py index 59eb1e96..852d44c3 100644 --- a/src/IntuneCD/update_proactiveRemediation.py +++ b/src/IntuneCD/update_proactiveRemediation.py @@ -22,7 +22,7 @@ ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Proactive Remediation in Intune if, the configuration in Intune differs from the JSON/YAML file. @@ -172,7 +172,9 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"deviceHealthScriptAssignments": update} post_assignment_update( @@ -202,7 +204,7 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = {"deviceHealthScriptAssignments": assignment} diff --git a/src/IntuneCD/update_profiles.py b/src/IntuneCD/update_profiles.py index fa455e19..25ed4e0f 100644 --- a/src/IntuneCD/update_profiles.py +++ b/src/IntuneCD/update_profiles.py @@ -22,7 +22,7 @@ ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Device Configurations in Intune, if the configuration in Intune differs from the JSON/YAML file. @@ -75,7 +75,6 @@ def update(path, token, assignment=False, report=False): data["value"] = val if data["value"]: - print("-" * 90) mem_id = data["value"]["id"] # Remove keys before using DeepDiff @@ -124,7 +123,6 @@ def update(path, token, assignment=False, report=False): # If any changed values are found, push them to # Intune if pdiff or cdiff: - if report is False: payload = plistlib.dumps(repo_payload_config) repo_data["payload"] = str( @@ -206,7 +204,6 @@ def update(path, token, assignment=False, report=False): for mem_omaSetting, repo_omaSetting in zip( data["value"]["omaSettings"], repo_data["omaSettings"] ): - diff = DeepDiff( mem_omaSetting, repo_omaSetting, @@ -277,7 +274,9 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"assignments": update} post_assignment_update( @@ -328,7 +327,7 @@ def update(path, token, assignment=False, report=False): mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = {"assignments": assignment} diff --git a/src/IntuneCD/update_shellScripts.py b/src/IntuneCD/update_shellScripts.py index d5da4cb6..148239b8 100644 --- a/src/IntuneCD/update_shellScripts.py +++ b/src/IntuneCD/update_shellScripts.py @@ -24,7 +24,7 @@ ) -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Shell scripts in Intune if the configuration in Intune differs from the JSON/YAML file. @@ -137,7 +137,9 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: request_data = {"deviceManagementScriptAssignments": update} post_assignment_update( @@ -166,7 +168,7 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: request_data = { diff --git a/src/IntuneCD/update_windowsEnrollmentProfile.py b/src/IntuneCD/update_windowsEnrollmentProfile.py index 3b6ac8ca..8b6902b9 100644 --- a/src/IntuneCD/update_windowsEnrollmentProfile.py +++ b/src/IntuneCD/update_windowsEnrollmentProfile.py @@ -20,7 +20,7 @@ ENDPOINT = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles" -def update(path, token, assignment=False, report=False): +def update(path, token, assignment=False, report=False, create_groups=False): """ This function updates all Windows Enrollment Profiles in Intune, if the configuration in Intune differs from the JSON/YAML file. @@ -99,17 +99,20 @@ def update(path, token, assignment=False, report=False): if assignment: mem_assign_obj = get_object_assignment(mem_id, mem_assignments) - update = update_assignment(assign_obj, mem_assign_obj, token) + update = update_assignment( + assign_obj, mem_assign_obj, token, create_groups + ) if update is not None: - request_data = {"target": update} - post_assignment_update( - request_data, - mem_id, - "deviceManagement/windowsAutopilotDeploymentProfiles", - "assign", - token, - status_code=201, - ) + for target in update: + request_data = {"target": target["target"]} + post_assignment_update( + request_data, + mem_id, + "deviceManagement/windowsAutopilotDeploymentProfiles", + "assignments", + token, + status_code=201, + ) # If Autopilot profile does not exist, create it and assign else: @@ -129,18 +132,19 @@ def update(path, token, assignment=False, report=False): ) mem_assign_obj = [] assignment = update_assignment( - assign_obj, mem_assign_obj, token + assign_obj, mem_assign_obj, token, create_groups ) if assignment is not None: - request_data = {"target": assignment[0]["target"]} - post_assignment_update( - request_data, - post_request["id"], - "deviceManagement/windowsAutopilotDeploymentProfiles", - "assignments", - token, - status_code=201, - ) + for target in assignment: + request_data = {"target": target["target"]} + post_assignment_update( + request_data, + post_request["id"], + "deviceManagement/windowsAutopilotDeploymentProfiles", + "assignments", + token, + status_code=201, + ) print( "Autopilot profile created with id: " + post_request["id"] ) diff --git a/tests/Update/test_update_assignment.py b/tests/Update/test_update_assignment.py index 96eb6b82..11e4aa85 100644 --- a/tests/Update/test_update_assignment.py +++ b/tests/Update/test_update_assignment.py @@ -1,7 +1,11 @@ import unittest from unittest.mock import patch -from src.IntuneCD.update_assignment import update_assignment, get_added_removed, post_assignment_update +from src.IntuneCD.update_assignment import ( + update_assignment, + get_added_removed, + post_assignment_update, +) class TestUpdateAssignment(unittest.TestCase): @@ -33,10 +37,14 @@ def setUp(self): } ] self.request_data = {"value": [{"id": "12345", "displayName": "test"}]} - self.makeapirequest_patch = patch("src.IntuneCD.update_assignment.makeapirequest") + self.makeapirequest_patch = patch( + "src.IntuneCD.update_assignment.makeapirequest" + ) self.makeapirequest = self.makeapirequest_patch.start() - self.makeapirequestPost_patch = patch("src.IntuneCD.update_assignment.makeapirequestPost") + self.makeapirequestPost_patch = patch( + "src.IntuneCD.update_assignment.makeapirequestPost" + ) self.makeapirequestPost = self.makeapirequestPost_patch.start() def tearDown(self): @@ -47,13 +55,19 @@ def test_get_added_removed(self): """The list of added and removed should be returned.""" result = get_added_removed(self.output_data) - self.assertEqual(result, ["intent: apply, Filter ID: 1234, Filter Type: device, target: All Devices"]) + self.assertEqual( + result, + [ + "intent: apply, Filter ID: 1234, Filter Type: device, target: All Devices" + ], + ) def test_update_assignment(self): - self.makeapirequest.return_value = self.request_data - result = update_assignment(self.repo_data, self.mem_data, self.token) + result = update_assignment( + self.repo_data, self.mem_data, self.token, create_groups=True + ) self.assertEqual( result, diff --git a/tests/Update/test_update_windowsEnrollmentProfile.py b/tests/Update/test_update_windowsEnrollmentProfile.py index 0e43cdc9..5003b342 100644 --- a/tests/Update/test_update_windowsEnrollmentProfile.py +++ b/tests/Update/test_update_windowsEnrollmentProfile.py @@ -16,7 +16,11 @@ def setUp(self): self.directory = TempDirectory() self.directory.create() self.directory.makedir("Enrollment Profiles/Windows") - self.directory.write("Enrollment Profiles/Windows/test.json", '{"test": "test"}', encoding="utf-8") + self.directory.write( + "Enrollment Profiles/Windows/test.json", + '{"test": "test"}', + encoding="utf-8", + ) self.token = "token" self.mem_data = { "value": [ @@ -38,30 +42,47 @@ def setUp(self): "assignments": [{"target": {"groupName": "test1"}}], } - self.batch_assignment_patch = patch("src.IntuneCD.update_windowsEnrollmentProfile.batch_assignment") + self.batch_assignment_patch = patch( + "src.IntuneCD.update_windowsEnrollmentProfile.batch_assignment" + ) self.batch_assignment = self.batch_assignment_patch.start() - self.object_assignment_patch = patch("src.IntuneCD.update_windowsEnrollmentProfile.get_object_assignment") + self.object_assignment_patch = patch( + "src.IntuneCD.update_windowsEnrollmentProfile.get_object_assignment" + ) self.object_assignment = self.object_assignment_patch.start() - self.makeapirequest_patch = patch("src.IntuneCD.update_windowsEnrollmentProfile.makeapirequest") + self.makeapirequest_patch = patch( + "src.IntuneCD.update_windowsEnrollmentProfile.makeapirequest" + ) self.makeapirequest = self.makeapirequest_patch.start() self.makeapirequest.return_value = self.mem_data - self.update_assignment_patch = patch("src.IntuneCD.update_windowsEnrollmentProfile.update_assignment") + self.update_assignment_patch = patch( + "src.IntuneCD.update_windowsEnrollmentProfile.update_assignment" + ) self.update_assignment = self.update_assignment_patch.start() + self.update_assignment.return_value = [{"target": {"groupName": "test"}}] - self.load_file_patch = patch("src.IntuneCD.update_windowsEnrollmentProfile.load_file") + self.load_file_patch = patch( + "src.IntuneCD.update_windowsEnrollmentProfile.load_file" + ) self.load_file = self.load_file_patch.start() self.load_file.return_value = self.repo_data - self.post_assignment_update_patch = patch("src.IntuneCD.update_windowsEnrollmentProfile.post_assignment_update") + self.post_assignment_update_patch = patch( + "src.IntuneCD.update_windowsEnrollmentProfile.post_assignment_update" + ) self.post_assignment_update = self.post_assignment_update_patch.start() - self.makeapirequestPatch_patch = patch("src.IntuneCD.update_windowsEnrollmentProfile.makeapirequestPatch") + self.makeapirequestPatch_patch = patch( + "src.IntuneCD.update_windowsEnrollmentProfile.makeapirequestPatch" + ) self.makeapirequestPatch = self.makeapirequestPatch_patch.start() - self.makeapirequestPost_patch = patch("src.IntuneCD.update_windowsEnrollmentProfile.makeapirequestPost") + self.makeapirequestPost_patch = patch( + "src.IntuneCD.update_windowsEnrollmentProfile.makeapirequestPost" + ) self.makeapirequestPost = self.makeapirequestPost_patch.start() self.makeapirequestPost.return_value = {"id": "0"} diff --git a/tests/test_graph_batch.py b/tests/test_graph_batch.py index ad99f25c..53ebac27 100644 --- a/tests/test_graph_batch.py +++ b/tests/test_graph_batch.py @@ -7,7 +7,13 @@ import unittest from unittest.mock import patch -from src.IntuneCD.graph_batch import batch_request, batch_assignment, batch_intents, get_object_assignment, get_object_details +from src.IntuneCD.graph_batch import ( + batch_request, + batch_assignment, + batch_intents, + get_object_assignment, + get_object_details, +) class TestGraphBatch(unittest.TestCase): @@ -18,10 +24,36 @@ def setUp(self): self.batch_request_data = ["1", "2", "3"] self.batch_assignment_data = {"value": [{"id": "0"}]} self.batch_intents_data = { - "value": [{"id": "0", "templateId": "0", "displayName": "test", "description": "", "roleScopeTagIds": ["0"]}] + "value": [ + { + "id": "0", + "templateId": "0", + "displayName": "test", + "description": "", + "roleScopeTagIds": ["0"], + } + ] } - self.responses = [{"value": [{"target": {"groupId": "0", "deviceAndAppManagementAssignmentFilterId": "0"}}]}] - self.group_responses = [{"displayName": "test", "id": "0"}] + self.responses = [ + { + "value": [ + { + "target": { + "groupId": "0", + "deviceAndAppManagementAssignmentFilterId": "0", + } + } + ] + } + ] + self.group_responses = [ + { + "displayName": "test", + "id": "0", + "groupTypes": ["DynamicMembership"], + "membershipRule": "test", + } + ] self.filter_responses = [{"displayName": "test", "id": "0"}] self.category_responses = [ { @@ -44,13 +76,19 @@ def setUp(self): } ] - self.makeapirequestPost_patch = patch("src.IntuneCD.graph_batch.makeapirequestPost") + self.makeapirequestPost_patch = patch( + "src.IntuneCD.graph_batch.makeapirequestPost" + ) self.makeapirequestPost = self.makeapirequestPost_patch.start() - self.get_object_details_patch = patch("src.IntuneCD.graph_batch.get_object_details") + self.get_object_details_patch = patch( + "src.IntuneCD.graph_batch.get_object_details" + ) self.get_object_details = self.get_object_details_patch.start() - self.get_object_assignment_patch = patch("src.IntuneCD.graph_batch.get_object_assignment") + self.get_object_assignment_patch = patch( + "src.IntuneCD.graph_batch.get_object_assignment" + ) self.get_object_assignment = self.get_object_assignment_patch.start() self.batch_intents_patch = patch("src.IntuneCD.graph_batch.batch_intents") @@ -61,7 +99,11 @@ def setUp(self): self.batch_request_patch = patch("src.IntuneCD.graph_batch.batch_request") self.batch_request = self.batch_request_patch.start() - self.batch_request.side_effect = self.responses, self.group_responses, self.filter_responses + self.batch_request.side_effect = ( + self.responses, + self.group_responses, + self.filter_responses, + ) def tearDown(self): self.makeapirequestPost.stop() @@ -74,10 +116,19 @@ def tearDown(self): def test_batch_request(self): """The batch request function should return the expected result.""" - self.expected_result = [{"odata.count": 1, "value": [{"id": "0", "displayName": "test"}]}] + self.expected_result = [ + {"odata.count": 1, "value": [{"id": "0", "displayName": "test"}]} + ] self.makeapirequestPost.return_value = { "responses": [ - {"id": "5", "status": 200, "body": {"odata.count": 1, "value": [{"id": "0", "displayName": "test"}]}} + { + "id": "5", + "status": 200, + "body": { + "odata.count": 1, + "value": [{"id": "0", "displayName": "test"}], + }, + } ] } self.result = batch_request(self.batch_request_data, "test", "test", self.token) @@ -88,9 +139,23 @@ def test_batch_assignment(self): """The batch assignment function should return the expected result.""" self.expected_result = [ - {"value": [{"target": {"deviceAndAppManagementAssignmentFilterId": "test", "groupId": "0", "groupName": "test"}}]} + { + "value": [ + { + "target": { + "deviceAndAppManagementAssignmentFilterId": "test", + "groupId": "0", + "groupName": "test", + "groupType": "DynamicMembership", + "membershipRule": "test", + } + } + ] + } ] - self.result = batch_assignment(self.batch_assignment_data, "test", "test", self.token) + self.result = batch_assignment( + self.batch_assignment_data, "test", "test", self.token + ) self.assertEqual(self.result, self.expected_result) @@ -98,13 +163,32 @@ def test_batch_assignment_appProtection_mdmWindowsInformationProtectionPolicy(se """The batch assignment function should return the expected result for the platform.""" self.batch_assignment_data = { - "value": [{"id": "0", "@odata.type": "#microsoft.graph.mdmWindowsInformationProtectionPolicy"}] + "value": [ + { + "id": "0", + "@odata.type": "#microsoft.graph.mdmWindowsInformationProtectionPolicy", + } + ] } self.expected_result = [ - {"value": [{"target": {"deviceAndAppManagementAssignmentFilterId": "test", "groupId": "0", "groupName": "test"}}]} + { + "value": [ + { + "target": { + "deviceAndAppManagementAssignmentFilterId": "test", + "groupId": "0", + "groupName": "test", + "groupType": "DynamicMembership", + "membershipRule": "test", + } + } + ] + } ] - self.result = batch_assignment(self.batch_assignment_data, "test", "test", self.token, app_protection=True) + self.result = batch_assignment( + self.batch_assignment_data, "test", "test", self.token, app_protection=True + ) self.assertEqual(self.result, self.expected_result) @@ -112,32 +196,72 @@ def test_batch_assignment_appProtection_windowsInformationProtectionPolicy(self) """The batch assignment function should return the expected result for the platform.""" self.batch_assignment_data = { - "value": [{"id": "0", "@odata.type": "#microsoft.graph.windowsInformationProtectionPolicy"}] + "value": [ + { + "id": "0", + "@odata.type": "#microsoft.graph.windowsInformationProtectionPolicy", + } + ] } self.expected_result = [ - {"value": [{"target": {"deviceAndAppManagementAssignmentFilterId": "test", "groupId": "0", "groupName": "test"}}]} + { + "value": [ + { + "target": { + "deviceAndAppManagementAssignmentFilterId": "test", + "groupId": "0", + "groupName": "test", + "groupType": "DynamicMembership", + "membershipRule": "test", + } + } + ] + } ] - self.result = batch_assignment(self.batch_assignment_data, "test", "test", self.token, app_protection=True) + self.result = batch_assignment( + self.batch_assignment_data, "test", "test", self.token, app_protection=True + ) self.assertEqual(self.result, self.expected_result) def test_batch_assignment_appProtection_iosManagedAppProtection(self): """The batch assignment function should return the expected result for the platform.""" - self.batch_assignment_data = {"value": [{"id": "0", "@odata.type": "#microsoft.graph.iosManagedAppProtection"}]} + self.batch_assignment_data = { + "value": [ + {"id": "0", "@odata.type": "#microsoft.graph.iosManagedAppProtection"} + ] + } self.expected_result = [ - {"value": [{"target": {"deviceAndAppManagementAssignmentFilterId": "test", "groupId": "0", "groupName": "test"}}]} + { + "value": [ + { + "target": { + "deviceAndAppManagementAssignmentFilterId": "test", + "groupId": "0", + "groupName": "test", + "groupType": "DynamicMembership", + "membershipRule": "test", + } + } + ] + } ] - self.result = batch_assignment(self.batch_assignment_data, "test", "test", self.token, app_protection=True) + self.result = batch_assignment( + self.batch_assignment_data, "test", "test", self.token, app_protection=True + ) self.assertEqual(self.result, self.expected_result) def test_batch_intents(self): """The batch intents function should return the expected result.""" - self.batch_request.side_effect = self.category_responses, self.settings_responses + self.batch_request.side_effect = ( + self.category_responses, + self.settings_responses, + ) self.expected_result = { "value": [ { diff --git a/tests/test_group_report.py b/tests/test_group_report.py new file mode 100644 index 00000000..166ee826 --- /dev/null +++ b/tests/test_group_report.py @@ -0,0 +1,81 @@ +import unittest +import json +import os + +from testfixtures import TempDirectory +from src.IntuneCD.assignment_report import get_group_report + + +class TestGetGroupReport(unittest.TestCase): + def setUp(self): + self.directory = TempDirectory() + self.directory.create() + self.directory.makedir("Device Configurations") + self.directory.makedir("Compliance Policies/Policies") + self.directory.write( + "Device Configurations/test.json", + '{"displayName": "testconfig", "assignments": [{"target": {"groupName": "test1", "groupType": "DynamicMembership", "membershipRule": "test"}}]} ', + encoding="utf-8", + ) + self.directory.write( + "Compliance Policies/Policies/test.json", + '{"displayName": "testconfig", "assignments": [{"target": {"groupName": "test1", "groupType": "DynamicMembership", "membershipRule": "test"}}]} ', + encoding="utf-8", + ) + + def tearDown(self): + # Remove the test folder and all its contents + self.directory.cleanup() + + def test_get_group_report(self): + # Test that the function returns the expected output for the sample file + expected_output = [ + { + "assignedTo": { + "Compliance Policies": ["testconfig"], + "Device Configurations": ["testconfig"], + }, + "groupName": "test1", + "groupType": "DynamicMembership", + "membershipRule": "test", + } + ] + get_group_report(self.directory.path, "json") + with open( + os.path.join(self.directory.path, "Assignment Report", "report.json"), "r" + ) as f: + actual_output = eval(f.read()) + self.assertEqual(actual_output, expected_output) + + def test_get_group_report_no_files(self): + # Test that the function returns an empty list when no files are found + with open( + os.path.join(self.directory.path, "Device Configurations", "test.json"), "r" + ) as f: + data = json.load(f) + with open( + os.path.join(self.directory.path, "Device Configurations", "test.json"), "w" + ) as f: + data.pop("assignments") + data = json.dump(data, f) + with open( + os.path.join( + self.directory.path, "Compliance Policies/Policies", "test.json" + ), + "r", + ) as f: + data = json.load(f) + with open( + os.path.join( + self.directory.path, "Compliance Policies/Policies", "test.json" + ), + "w", + ) as f: + data.pop("assignments") + data = json.dump(data, f) + + get_group_report(self.directory.path, "json") + path_exists = os.path.exists( + os.path.join(self.directory.path, "Assignment Report") + ) + self.assertEqual(path_exists, False)