Microsoft 365 – Governance implementation – Detecting Inactive Users in SharePoint Site Collections

“The pessimist sees difficulty in every opportunity. The optimist sees opportunity in every difficulty.”
Greetings, Microsoft 365 enthusiasts!
Join me on a journey through the dynamic realm of user management, where we unravel the intricacies of identifying and handling users lingering in SharePoint site collections post their departure from Azure AD. In this insightful guide, we’ll delve into the creation of a powerful .NET Core application, leveraging the capabilities of Microsoft Graph APIs.
Let’s empower ourselves with the tools to navigate the evolving landscape of user presence, ensuring the security, compliance, and seamless user management of our Microsoft 365 environment. Ready to embark on this illuminating adventure? Let’s dive in!
Key Takeaways from this article
- Explore how to build a .NET Core application using Microsoft Graph APIs.
- Identify and gather information about users lingering in SharePoint after removal from Azure AD.
- Understand the crucial role of permissions in Microsoft Graph API and their impact on user management.
- Delve into a real-world example of detecting inactive users in SharePoint for enhanced security and compliance.
- Code walkthrough: Uncover the process of identifying removed users in SharePoint site collections step by step.
By the end, you’ll be equipped with valuable insights and practical know-how for efficient user management in your Microsoft 365 environment. Let’s empower your Microsoft 365 journey!
Objective
The goal is to create a .NET Core application using Microsoft Graph APIs to detect users who are no longer part of Azure AD but still have traces in SharePoint site collections. By querying the User Information Lists in various site collections, we can identify users and check their existence in Azure AD.
Prerequisites
Before diving into the implementation, ensure you have the following prerequisites:
- A valid Microsoft 365 account with the necessary permissions.
- Access to Microsoft Graph APIs for user-related queries.
- .NET Core SDK installed on your development machine.
Real-World Example: Detecting Inactive Users in SharePoint
Let’s explore a real-world scenario to illustrate how the code provided can be utilized to detect inactive users in SharePoint site collections. As IT administrators responsible for maintaining user directories and ensuring compliance in a dynamic Microsoft 365 environment, we encounter various challenges.
Scenario
Organization Overview:
- We are part of a growing organization that heavily relies on Microsoft 365 for collaboration and document management.
- SharePoint serves as a key platform for creating team sites, document libraries, and project spaces.
Challenge:
Over time, employees join and leave the organization. Some users are removed from Azure AD, but their artifacts (user entries) linger in SharePoint site collections. This situation poses potential security risks and impedes accurate user management.
Benefits
- Enhanced Security: By identifying and removing traces of inactive users, we ensure that access to sensitive information is restricted to authorized personnel.
- Compliance: The tool aids in compliance with security protocols and data protection regulations by maintaining an accurate user directory.
- Streamlined User Management: Regularly running this tool contributes to a clean and efficient user management process within Microsoft 365.
This real-world example demonstrates how the provided .NET Core application can be a valuable asset for IT administrators dealing with user management in Microsoft 365. By customizing and extending this tool, we can address specific organizational needs and contribute to a more secure and compliant environment.
Understanding Microsoft Graph API Permissions for User Management
When working with Microsoft Graph API to manage users in Microsoft 365, understanding and correctly configuring permissions is crucial. Permissions define the scope of actions an application can perform on behalf of a user or the organization. In this detailed guide, we’ll explore the necessary Graph API permissions, why they are essential, and the potential impact if not used correctly.
Grant Permissions
- Navigate to our registered application in the Microsoft Entra ID portal and grant the necessary permissions. If you are unsure how to do this, refer to below guide for step-by-step instructions – https://knowledge-junction.in/2024/01/18/microsoft-entra-registering-new-application-and-assigning-permissions-to-access-microsoft-graph-apis/
Microsoft Graph API Permissions
- User.Read.All and User.ReadWrite.All:
- Purpose: These permissions are required to read and write user profiles. They enable applications to access user information, including attributes like usernames, email addresses, and profile pictures.
- Why Needed: To identify and manage users effectively, we need to retrieve and update their information. These permissions ensure the application can read and modify user profiles across the entire organization.
- Sites.Read.All and Sites.ReadWrite.All:
- Purpose: Essential for accessing SharePoint site collections and their properties. Required to fetch information about SharePoint sites and user-related data stored in them.
- Why Needed: In scenarios like detecting inactive users across SharePoint site collections, these permissions enable the application to traverse sites, retrieve user lists, and analyze user-related data.
- Directory.Read.All and Directory.ReadWrite.All:
- Purpose: Permissions to read and write directory data, including users, groups, and organizational structure.
- Why Needed: Managing users involves interacting with Azure AD directory services. These permissions are necessary for querying user details, verifying user existence, and maintaining accurate user directories.
- Group.Read.All and Group.ReadWrite.All:
- Purpose: Permissions to read and write group memberships, enabling the application to manage user memberships in security groups or distribution lists.
- Why Needed: In scenarios where user access is group-based, these permissions allow the application to update group memberships, ensuring proper access control.
- User.ReadBasic.All:
- Purpose: Provides basic read access to user profiles, including user IDs and display names.
- Why Needed: In situations where detailed user information is not required, these permissions offer a lightweight option for basic user identification.
Impact of Incorrect or Insufficient Permissions
- Authentication Failures:
- If the application lacks the required permissions, authentication will fail when attempting to access certain endpoints. This can lead to incomplete or inaccurate data retrieval.
- Limited Functionality:
- Insufficient permissions restrict the application’s functionality. For example, without the ability to read user profiles, the application cannot accurately identify inactive users.
- Security Risks:
- Inadequate permissions may lead to security risks. For instance, if an application lacks the necessary write permissions, it cannot remove traces of inactive users, potentially leaving sensitive data exposed.
- Compliance Issues:
- In scenarios where compliance requires accurate user management, insufficient permissions can result in non-compliance. This may lead to audit failures and regulatory issues.
Best Practices
- Principle of Least Privilege (PoLP): Grant only the permissions necessary for the application’s functionality. Avoid using more extensive permissions than required.
- Regular Auditing: Periodically review and audit the permissions assigned to applications. Update them based on evolving requirements.
- Conditional Access Policies: Implement conditional access policies to control access based on specific conditions, adding an extra layer of security.
- User Consent Policies: Understand and implement user consent policies to ensure users are aware of and approve the permissions requested by applications.
Configuring Microsoft Graph API permissions correctly is fundamental to the success and security of applications that interact with Microsoft 365 services. By understanding the purpose of each permission, adhering to best practices, and regularly auditing permissions, we can ensure a robust and compliant user management system within the Microsoft 365 environment.
Code Walkthrough: Uncovering Removed Users in SharePoint Site Collections using Microsoft Graph API
Let’s break down the provided code line by line:
private async void OnFindRemovedUsersClick(object sender, RoutedEventArgs e)
{
DateTime startTime = DateTime.Now;
try
{
// Get the authenticated Graph client
var graphClient = await _graphService.GetAuthenticatedGraphClient();
- Explanation: This method, named
OnFindRemovedUsersClick, is likely an event handler for a button click or a similar user interaction. It initiates the process of finding removed users.DateTime startTime = DateTime.Now;: Initializes a variable to store the current date and time, marking the start of the operation.var graphClient = await _graphService.GetAuthenticatedGraphClient();: Asynchronously obtains an authenticated Microsoft Graph client. The_graphServiceobject is assumed to be an instance of a service or class responsible for handling Microsoft Graph interactions.
// Fetch all sites in the tenant
int top = 978;
List<Microsoft.Graph.Models.Site>? siteCollections;
- Explanation: Sets up variables for fetching site collections from the Microsoft 365 tenant.
int top = 978;: Specifies the maximum number of site collections to retrieve in a single request.List<Microsoft.Graph.Models.Site>? siteCollections;: Declares a nullable list to store Microsoft Graph site collection objects.
// Initialize a list to store information about removed users and their corresponding sites
List<string> removedUserSites = new List<string>();
- Explanation: Creates a list to store information about users who have been removed along with details about the corresponding sites.
List<string> removedUserSites = new List<string>();: Initializes an empty list to hold strings representing user and site information.
// Fetch a page of site collections
var siteCollection = await graphClient.Sites.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Top = top;
});
siteCollections = siteCollection?.Value;
if (siteCollections != null)
{
- Explanation: Retrieves a page of site collections using the authenticated Graph client.
await graphClient.Sites.GetAsync((requestConfiguration) => {...});: Makes an asynchronous request to the Microsoft Graph API to get a page of site collections, applying aTopquery parameter to limit the number of results.siteCollections = siteCollection?.Value;: Retrieves the list of site collections from the response.if (siteCollections != null) {...}: Checks if site collections were successfully retrieved.
// Fetch User Information List ID and users in batches
var tasks = siteCollections.Select(async site =>
{
var siteId = site.Id.Split(',')[1];
var userListIdResponse = await graphClient.Sites[siteId].Lists
.GetAsync((requestConfiguration) =>
{
// Filter to get the User Information List
requestConfiguration.QueryParameters.Filter = "DisplayName eq 'User Information List'";
});
var userListId = userListIdResponse.Value?.FirstOrDefault()?.Id;
if (userListId != null)
{
- Explanation: Initiates tasks to fetch User Information List IDs and users for each site collection.
var tasks = siteCollections.Select(async site => {...});: Uses LINQSelectto create a list of asynchronous tasks, each handling a site collection.var siteId = site.Id.Split(',')[1];: Extracts the site ID from the site collection object.await graphClient.Sites[siteId].Lists.GetAsync(...);: Asynchronously retrieves the lists for the current site collection.var userListId = userListIdResponse.Value?.FirstOrDefault()?.Id;: Extracts the ID of the User Information List for the current site.if (userListId != null) {...}: Checks if the User Information List ID was successfully obtained.
var users = await graphClient.Sites[siteId].Lists[userListId].Items
.GetAsync((requestConfiguration) =>
{
// Expand the fields to include 'id' and 'UserName'
requestConfiguration.QueryParameters.Expand = new string[] { "fields($select=id,UserName)" };
});
var myusers = users.Value;
foreach (var listItem in myusers)
{
var userId = listItem.Id;
var fields = listItem.Fields;
if (fields != null && fields.AdditionalData != null && fields.AdditionalData.TryGetValue("UserName", out var userNameValue))
{
var userName = userNameValue?.ToString();
if (!string.IsNullOrEmpty(userName))
{
// Check if the user is removed from Azure AD
var isUserRemoved = await IsUserRemovedFromAzureAD(graphClient, userName);
if (isUserRemoved)
{
removedUserSites.Add($"User ID: {userId}, UserName: {userName}, Site: {site.DisplayName}");
}
}
}
}
}
});
await Task.WhenAll(tasks);
}
- Explanation: Retrieves user information from User Information Lists for each site collection.
await graphClient.Sites[siteId].Lists[userListId].Items.GetAsync(...);: Asynchronously retrieves items (users) from the User Information List for the current site collection.foreach (var listItem in myusers) {...}: Iterates through the retrieved user items.if (fields != null && fields.AdditionalData != null && fields.AdditionalData.TryGetValue("UserName", out var userNameValue)) {...}: Checks if the user item contains the “UserName” field.var isUserRemoved = await IsUserRemovedFromAzureAD(graphClient, userName);: Calls a method to check if the user is removed from Azure AD.removedUserSites.Add($"User ID: {userId}, UserName: {userName}, Site: {site.DisplayName}");: Adds information about removed users and their corresponding sites to the list.
// Append to the result text box for removed users
ResultTextBox.Text += "\nRemoved Users and Corresponding Sites:\n";
foreach (var userSiteInfo in removedUserSites)
{
ResultTextBox.Text += userSiteInfo + "\n";
}
}
catch (Exception ex)
{
// Handle any exceptions and display an error message
ResultTextBox.Text += $"Error: {ex.Message}\n";
}
finally
{
DateTime endTime = DateTime.Now;
TimeSpan executionTime = endTime - startTime;
// Display execution time in ResultTextBox
ResultTextBox.Text += $"Execution Time: {executionTime.TotalMilliseconds} milliseconds\n{ResultTextBox.Text}";
}
}
- Explanation: Handles exceptions, updates the UI with information about removed users, and displays the execution time.
ResultTextBox.Text += "\nRemoved Users and Corresponding Sites:\n";: Updates the result text box to indicate the start of the removed users section.foreach (var userSiteInfo in removedUserSites) {...}: Iterates through the list of removed user information and appends it to the result text box.ResultTextBox.Text += $"Error: {ex.Message}\n";: If an exception occurs, displays an error message in the result text box.ResultTextBox.Text += $"Execution Time: {executionTime.TotalMilliseconds} milliseconds\n{ResultTextBox.Text}";: Appends the execution time information to the result text box.
This code is designed to find and display information about users who have been removed from Azure AD within the specified SharePoint site collections. It utilizes the Microsoft Graph API for these operations.
We have very good / detailed articles on Microsoft Graph. Kindly please have a look. – https://knowledge-junction.in/category/technology-articles/m365/microsoft-graph/
Conclusion
In this exploration of user management within Microsoft 365, we’ve successfully crafted a .NET Core application using Microsoft Graph APIs to address the challenge of identifying users lingering in SharePoint site collections despite their removal from Azure AD. By proactively detecting such cases, administrators can bolster security, ensure compliance, and streamline user management processes.
This robust tool not only enhances the accuracy of user directories but also contributes to a more secure and compliant Microsoft 365 environment. As we navigate the ever-evolving landscape of dynamic user presence, the implementation of such functionalities proves invaluable for IT administrators seeking efficient and reliable solutions.
Also get my article updates on my social media handles.
Twitter – https://twitter.com/PrajyotYawalkar?t=oovP0r9FnDtz5nNSJGKO0Q&s=09
LinkedIn – https://www.linkedin.com/in/prajyot-yawalkar-093716224/
Have a wonderful day.
Thanks for reading.
