SharePoint 2016: Check Permissions – Windows Auth

I’ve written several blog posts on the “Check Permissions” function and its reliance on the “External Token”, including one for SharePoint 2013 with Windows authentication, which you may consider as some good pre-reading to this post, as it discusses the External Token and the details around trying to refresh that token non-interactively.

Suffice to say that when a non-end-user SharePoint function like Check Permissions, Alerts, Workflow, etc needs to determine if a user has access to an item, it must periodically be able to refresh that users External Token, which means it must be able to find that user and their group memberships in Active Directory. Most commonly we see it fail to get group memberships for the user. If the user gets their permission to the site via AD group, then Check Permissions will say they have none, alert emails will fail to send, etc.

2013 vs 2016:

In SharePoint 2013, when a non-interactive function goes to get Active Directory group membership for a user, we call the method SPClaimsAuthRoleProvider.GetRolesForUserBestEffort(), which was plagued by several problems with the underlying .NET method GetAuthorizationGroups, as outlined in my previous post: https://joshroark.com/sharepoint-troubleshooting-check-permissions-windows-auth/

In SharePoint 2016, we call a different method: SPClaimsAuthRoleProvider.GetTokenGroupsForUser(). This appears to work a little better but is not fool proof. A lack of permissions in Active Directory for your app pool and / or farm service accounts will still make this fail.

Specifically, I found that a lack of the “Read remote access information” permission for the service accounts, on the end-user account will cause the token refresh to fail. Actually, the token will be refreshed, but it will not contain any AD groups, which means that SharePoint cannot tell if you should get any of the permissions given to those AD groups on the site.

I believe by default, all users in a domain will have this permission to all other user objects via the “Pre-Windows 2000 Compatible Access” group, which has NT Authority\Authenticated Users (all authenticated users) as a member.

If your SharePoint application pool account and farm service account (the one running the SharePoint timer service) do not have this permission to all user objects in AD, then they will be unable to properly refresh External Tokens for those users.

Also, I found that issue #4 from my 2013 post still applies to SharePoint 2016. We need permission to the default users and computers containers in AD.

Check Permissions in AD.

You should be able to check the permissions of your app pool account on your end user accounts using the “Effective Access” function in Active Directory Users and Computers (ADUC).

Find the end-user in ADUC. Right click and choose Properties, the Security tab, and hit the Advanced button.

Choose the Effective Access tab, choose your app pool account in the “Select a user” dialog, and choose “View effective access”.

You should see that it has a number of Read permissions to the account, including “Read Group Membership”, and “Read remote access information”.

Logging:

If you turn your SharePoint ULS logging up to Verbose, particularly for the “Claims Authentication” category, you should see a sequence like the following when you execute “Check Permissions” on an account that needs an External Token refresh. To be clear, this is what you should see when it works. Comparing this to your own logs should give you an indication to where it’s going wrong.

Important! You will only see the following sequence in the SharePoint logs if the users external token is expired at the time you run “check permissions”. If the token has not expired, the existing token is used, and none of the following is even attempted.

11/24/2019 15:44:40.26    w3wp.exe (0x1034)    0x0DC4    SharePoint Foundation    Flighting Infrastructure    asodk    Verbose    Got feature toggle. FeatureID: ‘AuthZenImprovedGetRolesForUserBestEffort’, Toggle: ‘True’    c8181b9f-90d7-e088-a10e-e030394a067a

11/24/2019 15:44:40.27    w3wp.exe (0x1034)    0x0DC4    SharePoint Foundation    Claims Authentication    a3umi    Verbose    SPClaimsAuthRoleProvider.GetTokenGroupsForUser() attempting to retrieve UserPrinicpal from principalContext. User: contoso\User1    c8181b9f-90d7-e088-a10e-e030394a067a

11/24/2019 15:44:40.30    w3wp.exe (0x1034)    0x0DC4    SharePoint Foundation    Claims Authentication    a3umk    Verbose    SPCLaimsAuthRoleProvider.GetTokenGroupsForUser() Retrieve UserPrinicpal from principalContext for user contoso\User1: Success.    c8181b9f-90d7-e088-a10e-e030394a067a

11/24/2019 15:44:40.30    w3wp.exe (0x1034)    0x0DC4    SharePoint Foundation    Claims Authentication    a3umm    Verbose    SPClaimsAuthRoleProvider.GetTokenGroupsForUser() Retrieve DirectoryEntry from UserPrincipal for user contoso\User1: Success.    c8181b9f-90d7-e088-a10e-e030394a067a

11/24/2019 15:44:40.32    w3wp.exe (0x1034)    0x0DC4    SharePoint Foundation    Claims Authentication    a3umn    Verbose    SPClaimsAuthRoleProvider.GetTokenGroupsForUser() loaded tokenGroups values for user contoso\User1.    c8181b9f-90d7-e088-a10e-e030394a067a

11/24/2019 15:44:40.32    w3wp.exe (0x1034)    0x0DC4    SharePoint Foundation    Claims Authentication    a3ump    Verbose    SPClaimsAuthRoleProvider.GetTokenGroupsForUser() Adding group with SID S-1-5-32-544.     c8181b9f-90d7-e088-a10e-e030394a067a

…And more of these “Adding group with SID…” events, one for each AD group the user is a member of.

Test Script:

Because the External Token is only refreshed every 24 hours by default, and users can refresh it themselves just by logging in, troubleshooting external token refresh problems can be difficult. As noted in my “Important!” note above, we have to be sure the external token has expired to even gather decent ULS logging.

To help troubleshoot this, I tweaked a PowerShell script that my peer Brent Person wrote. You should log on to one of the SharePoint web-front-ends as the application pool account* to most closely mimic what SharePoint is doing, and to get the most valid results. You should only need to specify the domain for the user, the users account name, and optionally, where you want it to output its log.

*Warning: Logging in as the app pool account can create other problems. See this to avoid them. Instead of logging into the server as that account, you may choose to do Shift + Right-click on PowerShell, choose “Run as different user” and log in as the app pool account there.

#Test-CheckPermissions2016
#Authors: Brpers; AdamSor; Joroar
#Date: 10/22/19
#Usage Notes: You need to run this from the SharePoint WFE, logged on as the application pool account for results to be most valid
#Specify values for these three variables: 
$domain="contoso.com"
$user="contoso\User1"
$logloc = "C:\temp\"

#Change nothing below this line:
$username = ($user.Split("\"))[1]
$assembly=[Reflection.Assembly]::Load("System.DirectoryServices.AccountManagement, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
$logfile = ($logloc+"Test_CheckPermissions2016_"+$username+"_"+(Get-Date -f yyyy-MM-dd)+".txt") 
$id = [System.DirectoryServices.AccountManagement.IdentityType]::Sid
[string]$groupType = "tokenGroups" 
$userEntry = New-Object System.DirectoryServices.DirectoryEntry
$principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Domain, $domain);
#Find the user in AD (tag_a3umi):
$adUser = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $user);
If(!$adUser)
{#We didn't even find the user in AD
Write-host -ForegroundColor Red "The user $user could NOT be found in domain $domain FindByIdentity failed. Check user and domain variables."
"ERROR: The user " + $user + " could NOT be found in domain " + $domain + " FindByIdentity failed." | out-file $logfile -Append
}
Else{
#We found the user in AD
Write-host "User Found: " $adUser.SamAccountName "|" $adUser.DisplayName "|" $adUser.DistinguishedName -ForegroundColor Yellow
"User Found: " + $adUser.SamAccountName + " | " + $adUser.DisplayName + " | " + $adUser.DistinguishedName | out-file $logfile -Append
#Get the underlying object (tag_a3umk):
$userEntry = $aduser.GetUnderlyingObject()
If(!$userEntry)
{
Write-host "GetUnderlyingObject FAILED" -ForegroundColor RED
"GetUnderlyingObject FAILED for user " + $adUser.SamAccountName | out-file $logfile -Append
}
#Refresh Cache (tag_a3umm):
$userEntry.RefreshCache($groupType)
#Get the Token Groups for the user (foreach under tag_a3umn)
$groups = $userEntry.Properties[$groupType]
If($groups){
#We found some group SIDs within the user object
#Resolve each group SID that we found (Try / Catch block under tag_a3umn)
ForEach($gr in $groups)
{
   $gsid = $null
   $gsid = (New-Object System.Security.Principal.SecurityIdentifier($gr, 0)).value
   $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($principalContext, $id, $gsid)
   If(!$group)
   {
        write-host "Failed to resolve SID for group" $gsid -ForegroundColor red
        "ERROR: Failed to resolve SID for group " + $gsid  | out-file $logfile -Append
   }
   Else
   {
        Write-Host "Group found:" $group.DistinguishedName  "|" $group.SID -ForegroundColor Green | ft -AutoSize
        "Group SID Resolved: " + $group.DistinguishedName + " | " + $group.SID | out-file $logfile -Append
   }  
}
}
Else{
#We found no group SIDs within the user object.  Suspect AD permisisons issue.
Write-host -ForegroundColor Red "No group SIDs were found in TokenGroups!"
Write-host -ForegroundColor Red "Likely the service account does NOT have enough permission to the user accounts in AD"
"ERROR: No group SIDs were found in TokenGroups!" | out-file $logfile -Append
}
}

2 Comments

Leave a Reply to Josh Roark Cancel reply