def apply(self, graph: Graph): """Creates the user with extension data and assigns the license.""" # Create user user_id = graph.create_user(self.user) # Add extension data (Tutti database ID) graph.add_user_extension(user_id, self.user.extension) # Assign Office 365 license (without Exchange) graph.assign_license(user_id=user_id, sku_id="6634e0ce-1a9f-428c-a498-f84ec7b8aa2e", disabled_plans=["9aaf7827-d63c-4b61-89c3-182f06f82e5c"])
def handle(self, *args, **options): try: graph = Graph.from_settings() self.stdout.write('Comparing user and group objects...') operations = aad_sync_objects(graph) # Split out delete operations delete_ops = [] non_delete_ops = [] for o in operations: if isinstance(o, DeleteUserOperation) or isinstance( o, DeleteGroupOperation): delete_ops.append(o) else: non_delete_ops.append(o) self.stdout.write('Create and update operations:') handle_operations(non_delete_ops, graph, self.stdout) self.stdout.write('Delete operations:') handle_operations(delete_ops, graph, self.stdout) self.stdout.write("Comparing group memberships...") operations = aad_sync_members(graph) self.stdout.write("Group membership operations:") handle_operations(operations, graph, self.stdout) # Do a sanity check for possible licensing issues self.stdout.write("Checking licenses...") users = graph.get_paged( "users", params={"$select": "userPrincipalName,assignedLicenses"}) without_license = [ u for u in users if not u.get("assignedLicenses") ] for u in without_license: self.stdout.write("User {} has no assigned licenses!".format( u["userPrincipalName"])) # Print orphans self.stdout.write("Checking extensions...") users = graph.get_paged("users", params={ "$select": "userPrincipalName", "$expand": "extensions" }) groups = graph.get_paged("groups", params={ "$select": "displayName", "$expand": "extensions" }) for o in users + groups: ext = o.get("extensions") if not ext or len( ext) != 1 or ext[0]["id"] != "nl.esmgquadrivium.tutti": self.stdout.write( "Unexpected extension for object: {}".format(o)) except HTTPError as e: self.stderr.write(str(e)) self.stderr.write(str(e.response.json()))
def aad_sync_objects(graph: Graph) -> List[SyncOperation]: """Gets the sync operations for syncing users and groups with AAD. Group memberships are synced separately and needs to be done after the operations returned here have been applied. """ operations = [] # Sync users local_users = [ convert_local_person(p) for p in Person.objects.filter_members() ] aad_users = graph.get_users() operations.extend(sync_users(local_users, aad_users)) # Sync groups local_groups = [convert_local_group(g) for g in QGroup.objects.all()] aad_groups = graph.get_groups() operations.extend(sync_groups(local_groups, aad_groups)) return operations
def get_user(graph: Graph, user_id: str): # Get user fields = [ 'id', 'displayName', 'userPrincipalName', 'identities', 'lastPasswordChangeDateTime', 'licenseAssignmentStates', 'passwordPolicies', 'passwordProfile', 'usageLocation', 'onPremisesImmutableId' ] params = {'$select': ','.join(fields), '$expand': 'extensions'} return graph.call_resource('users/{}'.format(user_id), params=params).json()
def aad_sync_members(graph: Graph) -> List[SyncOperation]: """Gets the sync operation for group membership adds/deletes. This uses the remote groups as the groups that will be synced, so this must be run after the aad_sync_objects operations have been applied on AAD. This is because the remote groups need to have been created already in order to be able to sync group memberships. """ operations = [] aad_users = graph.get_users() local_id_map = {u.extension["tuttiId"]: u for u in aad_users} remote_id_map = {u.directory_id: u for u in aad_users} for group in graph.get_groups(): # Get local and remote group members qs = Person.objects.filter(groups=group.extension["tuttiId"]) # Here we silently ignore local people that don't have a remote account local = [local_id_map[p.id] for p in qs if p.id in local_id_map] remote = [ remote_id_map[i] for i in graph.get_group_members(group.directory_id) ] # Sync operations.extend(sync_members(group, local, remote)) return operations
def setUp(self): if not settings.GRAPH_CLIENT_ID: self.skipTest("Microsoft Graph is not set up") return self.graph = Graph.from_settings() self.graph.extension_id = "nl.esmgquadrivium.tutti-test"
def apply(self, graph: Graph): graph.update_group(self.group.directory_id, self.changes)
def apply(self, graph: Graph): graph.delete_group(self.group.directory_id)
def apply(self, graph: Graph): group_id = graph.create_group(self.group) graph.add_group_extension(group_id, self.group.extension)
def apply(self, graph: Graph): graph.update_user(self.user.directory_id, self.changes)
def apply(self, graph: Graph): graph.delete_user(self.user.directory_id)
def apply(self, graph: Graph): graph.remove_group_member(self.group.directory_id, self.user.directory_id)