I gave a talk on the subject of this post on the 8th October 2020 for the Microsoft SharePoint Framework and JavaScript Special Interest Group. They have kindly recorded and editing the video below.
Every SharePoint site maintains its own list of users and groups. In this post we’ll explore the relationship between a site’s users and groups and the corresponding users and groups in Azure Active Directory (AAD).
Consider a solution to process purchase orders where different members of staff are authorised to approve order values up to certain limits. We can define rules to apply approval limits to users, AAD groups and SharePoint groups as follows:
When a user submits a purchase order the system needs to determine their approval limit so that the order can be automatically processed or delivered to another user for approval. We can determine a user’s approval limit by finding the rules (Purchase Order Limits items) which apply to that user.
The SharePoint Person or Group column type can be used to assign one or more users and groups to a rule. The selected users and groups are stored in the column as an array of IDs. Column formatting has been used in the above screenshot of the list to display those IDs in the Applies to IDs column.
To determine if a rule applies to a user, we need to find all IDs used to identify the user and their groups – i.e. their multiple identities – on the SharePoint site and match them against the values in the rule’s Applies to column.
What are SharePoint Site Users and Site Groups?
It is important to be clear whether we are talking about site users, site groups, organisation user accounts, security groups and Microsoft 365 groups.
What | Where | Manage using | |
---|---|---|---|
Organisation Users | Real users or service accounts | Azure Active Directory (AAD) | On-premises AD Azure AD Microsoft 365 Admin Center |
Organisation Groups | Active Directory security groups and Microsoft 365 groups | Azure Active Directory (AAD) | On-premises AD Azure AD Microsoft 365 Admin Center |
SharePoint Site Users | Organisation’s users and groups that have been referenced on a site | SharePoint Site | Cannot create directly Can view and delete in SP Site User Profile view |
SharePoint Site Groups | Used to organise collections of Site Users | SharePoint Site | SP Site Settings |
Each SharePoint site maintains its own list of users and groups. Site users can be assigned membership of a site group, but site groups cannot be nested as members of other site groups.
Site groups are created and maintained on the SharePoint site. They have no relation to Azure Active Directory Security Groups or Microsoft 365 Groups.
A SharePoint site creates site users to reference both an organisation’s users and groups. This means that security groups and Microsoft 365 groups can be members of SharePoint site groups.
Site users cannot be created directly in SharePoint, instead they are always created in response to some action which references the organisation user or group. This could be by:
- A user visiting the site;
- Granting permission to the user or group for the site, library, list or list item;
- Choosing the user in a People or Group column for a list or library item.
- Calling SharePoint EnsureUser service to add the user to the site.
The Site User and Group Information web part
To help explore the multiple identities a user has for a site SharePoint site I created the Site User and Group Information web part, now merged into the SharePoint Framework Client-Side Web Part Samples & Tutorial Materials.
If you are unable to build the web part yourself you can download a pre-built version here.
Deploy the package to your SharePoint Online app catalogue (alternative instructions) and then install on a site.
Next add the User and Group Info web part to a page on your site.
Demo site users and groups
In our demo tenant we’ve created security groups in Azure Active Directory as shown. The black arrows on the diagram indicate that a user or group is a member of the group being pointed to. E.g. Alex is a member of AD Group 3 which itself is a nested member of AD Group 1.
On our demo SharePoint site we have created two SharePoint site groups and assigned Grady and the AAD security groups as members.
Adele, Henrietta and AD Group 4 have been assigned read permissions on the site directly.
Using the web part
The User and Group Info web part allows us to search for tenant users or select from site users.
Start by selecting an existing site user:
Notice that users Diego, Alex and Isaiah are not present in the list of site users. This is because they have not accessed the site themselves and because they have not been directly assigned permissions to the site/lists/libraries/items or selected in any Person or Group columns in use on the site.
For our example we select Adele from the list of site users:
All the highlighted IDs in the above screenshot are:
- 11 – SP Group 1
- 12 – SP Group 2
- 14 – AD Group 2
- 15 – AD Group 1
- 17 – Adele Vance
The SharePoint site users and groups that refer to Adele are shown below:
If we click on these IDs in the web part we can view the profile of the user, security group or SharePoint group.
No matter which the type of principal ID (user, security group, SP group) we click on we always visit the userdisp.aspx
page, passing the ID value as a query parameter.
SharePoint will then either redirect to or display a page suitable for the type of principal we are viewing.
Clicking on 17 redirects to Adele’s Delve profile.
The link for SP Group 1 (with ID 11) redirects to SharePoint’s group profile page.
The link for AD Group 1 (with ID 15) isn’t redirected, but userdisp.aspx shows a basic USER profile.
From this screen shot we can see that SharePoint is treating the AAD security group as a site user.
All those IDs listed for Adele correspond to either site users or site groups, but there is no duplication of IDs between users or groups. Further, accessing userdisp.aspx
and passing either a site user or site group ID as a query parameter resulted in a redirect to a page appropriate for displaying a real user, an AAD group or a site group. We can surmise that site users and groups are maintained as a single internal list which is why the People and Groups column can mix selection of user or group values.
In the web part we select Grady from the list of site users and get the following results:
For Grady the highlighted IDs in the above screenshot are:
- 11 – SP Group 1
- 13 – Grady Archie
Mapping these results onto the users and groups diagram we get:
Next we’ll look at Isaiah’s Ids. Since Isaiah is not a site user we’ll need to search for him in the web part. Once Isaiah has been found the following results are displayed:
In this example Isaiah is not known to the SP site at all. This would change as soon as Isaiah attempted to visit the site, but right now the meantime the only way to refer to Isaiah is as site user, AD Group 4.
We can map Isaiah’s results onto the user and groups diagram:
Code
In this section we’ll look at some of the code used to implement the web part. The full source code is available in directory samples/react-sp-site-user-groups
from the sp-dev-fx-webparts repository.
Main Component
The web part makes use of React. The top-level react component is the UserAndGroupInfo
function. This component maintains state of the currently selected user and references to the services used to perform lookups against Graph and SP APIs.
The Graph and SP APIs are accessed using the PnP JS Library.
When the selected user changes, this component will create new lookup promises and pass them to child components for rendering once fulfilled:
<>
<UserInfo
context={props.context}
siteUserInfoPromise={userGroupLookup.getSpUserAndMemberGroupsPromise(siteUserId)}
aadUserPromise={aadUserGroupLookup.getAadUser(email)}
/>
<UserGroupMemberships
context={props.context}
membershipsPromise={userGroupLookup.getUserMemberships(siteUserId, email)}
/>
</>
Selecting Users
User selection is handled by the UserSelection
function which takes properties for the web part context, a SharePoint User Lookup service reference and a call-back to notify of selection changes:
export interface IUserSelectionProps {
context: WebPartContext;
userGroupLookup: SpUserGroupLookup;
onSelectedUserChanged: (siteUserId: number, email: string) => void;
}
The web part context is used by the People Picker control from the PnP SPFx Controls project. This control is used to search for users across the tenant, returning the email address of any selected user.
The SharePoint User Lookup service is used to retrieve the list of site users:
public async getSpSiteUsers(): Promise<ISiteUserInfo[]> {
return sp.web.siteUsers.filter("PrincipalType eq " + PrincipalType.User).get();
}
Using the People Picker control or the list of site users, a user is selected and notified to the parent components via the onSelectedUserChanged
call-back.
Displaying User (AAD + SP) Information
The main UserAndGroupInfo
parent component passes two promises to its UserInfo
child component as properties siteUserInfoPromise
and aadUserPromise
. These promise are retrieved by calls to the SpUserGroupLookup.getSpUserAndMemberGroupsPromise
() and AadUserGroupLookup.getAadUser()
methods respectively.
The getAadUser
method takes an email address parameter which can be left undefined if we want to retrieve the current user’s details. AAD user information is retrieved from the Graph API:
public async getAadUser(email: string | undefined): Promise<IUser> {
if (this.aadUserPromises.has(email)) {
return this.aadUserPromises.get(email);
} else {
let aadUserPromise: Promise<IUser>;
if (email) {
console.debug("Getting AAD user by email:", email);
aadUserPromise = graph.users.getById(email).get();
} else {
console.debug("Getting AAD user for current user");
aadUserPromise = graph.me() as Promise<IUser>;
}
this.aadUserPromises.set(email, aadUserPromise);
return aadUserPromise;
}
}
The API calls in the getAadUser
code listing have been highlighted. The other code is mostly concerned with logging and caching.
Even though the UserInfo
component doesn’t show group information, other components on the page will need group details. Therefore we use a single function, getSpUserAndMemberGroupsPromise
, to retrieve all the information needed once and cache the resulting promise for other lookups. getSpUserAndMemberGroupsPromise
takes a siteUserId
parameter which refers to the site user to be queried from the SharePoint site.
public getSpUserAndMemberGroupsPromise(siteUserId: number): Promise<ISiteUserInfo | undefined> {
if (this.spUserAndMemberGroupsPromises.has(siteUserId)) {
return this.spUserAndMemberGroupsPromises.get(siteUserId);
} else {
let spUserAndMemberGroupsPromise: Promise<ISiteUserInfo>;
if (siteUserId) {
spUserAndMemberGroupsPromise = sp.web.getUserById(siteUserId).expand("Groups").get();
} else if (siteUserId === 0) {
spUserAndMemberGroupsPromise = sp.web.currentUser.expand("Groups").get();
} else {
spUserAndMemberGroupsPromise = Promise.resolve(undefined);
}
this.spUserAndMemberGroupsPromises.set(siteUserId, spUserAndMemberGroupsPromise);
return spUserAndMemberGroupsPromise;
}
}
User Profile Synchronisation
An organisation’s user accounts and groups are created externally to SharePoint, using tools such as Azure Active Directory or the Microsoft 365 Admin Center. User and group accounts accounts are periodically synchronised to SharePoint where they can then be added to a site.
Microsoft describe the steps used to synchronise users from Azure AD to SharePoint here. Similar to other periodic processes in Microsoft 365, they don’t say how long it takes for an AAD user to become available in SharePoint, but in my experience new users are normally visible within a few minutes.
It isn’t clear from documentation what technology is behind the SharePoint Directory Store, but information about the overall synchronisation process says ‘Azure AD syncs data from Azure AD to the SharePoint directory store.’ This suggests that Azure AD is writing data into the SharePoint directory store, so we can reasonably assume it is an append-only log describing user and group changes in AD.
If we assume that the SharePoint directory store is an append-only log, then the User Profile Application must be driving the next stage of the synchronisation process by periodically checking for new log entries and processing them as user and group updates accordingly.
User Profile Content
Only minimal information about a user is stored on a SharePoint site, instead the User Profile Application maintains the user’s full profile.
The following is a snippet describing site user, Adele Vance, retrieved using the CLI for Microsoft 365:
> m365 spo user list --webUrl https://watconsdev.sharepoint.com/sites/SiteUsersDemo --output json
{
"Id": 17,
"IsHiddenInUI": false,
"LoginName": "i:0#.f|membership|adelev@watconsdev.onmicrosoft.com",
"Title": "Adele Vance",
"PrincipalType": 1,
"Email": "AdeleV@watconsdev.onmicrosoft.com",
"Expiration": "",
"IsEmailAuthenticationGuestUser": false,
"IsShareByEmailGuestUser": false,
"IsSiteAdmin": false,
"UserId": {
"NameId": "10032000e5bd0a0a",
"NameIdIssuer": "urn:federation:microsoftonline"
},
"UserPrincipalName": "adelev@watconsdev.onmicrosoft.com"
}
The User Profile Application maintains much more information about the user, synchronised from AAD and Exchange. The User Profile Application can be accessed from SharePoint Admin Center.