Check user permissions for users in trusted domains

%3CLINGO-SUB%20id%3D%22lingo-sub-817755%22%20slang%3D%22en-US%22%3ECheck%20user%20permissions%20for%20users%20in%20trusted%20domains%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-817755%22%20slang%3D%22en-US%22%3E%3CP%3EI%20have%20identified%20a%20change%20in%20how%20SharePoint%20handles%20the%20Check%20Permissions%20functionality%20from%20SharePoint%202013%20to%20SharePoint%202016%2F2019.%20If%20you%20have%20users%20in%20domain%20A%2C%20and%20a%20SharePoint%20farm%20and%20security%20groups%20in%20domain%20B%2C%20SharePoint%202016%2F2019%20will%20fail%20to%20show%20if%20a%20user%20has%20access%20to%20a%20site%20if%20he%20is%20granted%20access%20using%20security%20groups%20(from%20domain%20B).%3C%2FP%3E%3CP%3EThis%20is%20caused%20by%20a%20change%20in%20the%20internal%20static%20method%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3EMicrosoft.SharePoint.Administration.Claims.SPClaimsAuthRoleProviderGetRolesForUserBestEffort(string%20username)%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3E%3CFONT%3EIn%20SharePoint%202013%20the%20implementation%20of%20this%20method%20makes%20a%20call%20to%3C%2FFONT%3E%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3ESystem.DirectoryServices.AccountManagement.UserPrincipal()%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3E%3CFONT%3EIn%20SharePoint%202016%20and%202019%20this%20%3CFONT%3Emethod%20%3C%2FFONT%3Ewill%20never%20be%20called%20because%20of%20an%20enabled%20feature%20flag%20(%3CEM%3EAuthZenImprovedGetRolesForUserBestEffort%3C%2FEM%3E)%20that%20now%20changes%20the%20implementation%20to%20only%20make%20a%20call%20to%20the%20method%20%3C%2FFONT%3E%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3EMicrosoft.SharePoint.Administration.Claims.SPClaimsAuthRoleProvider.GetTokenGroupsForUser(PrincipalContext%20principalContext%2C%20SPClaim%20entity%2C%20List%3CSTRING%3E%20result)%3C%2FSTRING%3E%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3E%3CFONT%3EThe%20behavior%20in%20SharePoint%202016%20and%202019%20is%20the%20same%20as%20what%20would%20happen%20in%20SharePoint%202013%20if%20the%20SPWebApplication%20PeoplePicker%20property%20%3C%2FFONT%3E%3CFONT%3ESidHistorySafeMode%20or%20UseGlobalCatalog%20where%20set%20to%20true%20(both%20defaults%20to%20false).%3C%2FFONT%3E%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EMy%20questions%20are%3A%3C%2FP%3E%3CP%3EIs%20this%20design%20change%20intentional%2C%20and%20if%20so%2C%20what%20is%20the%20reason%20behind%20that%3F%20Does%20it%20mean%20that%20it's%20not%20supported%20to%20have%20a%20SharePoint%20(2016%2F2019)%20farm%20installation%20that%20has%20users%20in%20a%20separate%20domain%20like%20mentioned%20above%3F%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EIs%20this%20a%20bug%2Foversight%20or%20something%20that%20can%20be%20changed%20so%20that%20it's%20possible%20to%20correctly%20identify%20permissions%20for%20users%20in%20a%20trusted%20domain%3F%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3EIs%20there%20a%20workaround%20or%20alternate%20approach%20to%20successfully%20have%20the%20Check%20Permissions%20functionality%20work%20when%20having%20users%20in%20a%20separate%20trusted%20domain%3F%3C%2FP%3E%3CP%3E%3CBR%20%2F%3ESP2013%20code%3A%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3EUserPrincipal%20principal%20%3D%20null%3B%0Atry%0A%7B%0A%20%20%20%20principal%20%3D%20UserPrincipal.FindByIdentity(principalContext%2C%20entity.Value)%3B%0A%7D%0Acatch%0A%7B%0A%20%20%20%20principal%20%3D%20null%3B%0A%7D%0Aif%20(principal%20!%3D%20null)%0A%7B%0A%20%20%20%20userSecurityIdentifier%20%3D%20principal.Sid%3B%0A%20%20%20%20if%20(SidHistorySafeMode%20%7C%7C%20UseGlobalCatalog)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20GetTokenGroupsForUser(principalContext%2C%20principal%2C%20result%2C%20strDomain)%3B%0A%20%20%20%20%7D%0A%20%20%20%20else%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20foreach%20(Principal%20principal2%20in%20principal.GetAuthorizationGroups())%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20result.Add(SPClaimEncodingManager.EncodeClaimIntoFormsSuffix(SPClaimTypes.GroupSid%2C%20principal2.Sid.Value%2C%20SPClaimValueTypes.String%2C%20SPOriginalIssuers.Format(SPOriginalIssuerType.Windows)))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%7D%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CP%3ESP2019%20code%3A%3C%2FP%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3Eif%20(VariantConfiguration.IsGlobalExpFeatureToggleEnabled(ExpFeatureId.AuthZenImprovedGetRolesForUserBestEffort))%0A%7B%0A%20%20%20%20GetTokenGroupsForUser(principalContext%2C%20entity%2C%20result)%3B%0A%7D%0Aelse%0A%7B%0A%20%20%20%20UserPrincipal%20principal%20%3D%20null%3B%0A%20%20%20%20try%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20principal%20%3D%20UserPrincipal.FindByIdentity(principalContext%2C%20entity.Value)%3B%0A%20%20%20%20%7D%0A%20%20%20%20catch%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20principal%20%3D%20null%3B%0A%20%20%20%20%7D%0A%20%20%20%20if%20(principal%20!%3D%20null)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20userSecurityIdentifier%20%3D%20principal.Sid%3B%0A%20%20%20%20%20%20%20%20foreach%20(Principal%20principal2%20in%20principal.GetAuthorizationGroups())%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20result.Add(SPClaimEncodingManager.EncodeClaimIntoFormsSuffix(SPClaimTypes.GroupSid%2C%20principal2.Sid.Value%2C%20SPClaimValueTypes.String%2C%20SPOriginalIssuers.Format(SPOriginalIssuerType.Windows)))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%7D%3C%2FCODE%3E%3C%2FPRE%3E%3CP%3E%26nbsp%3B%3C%2FP%3E%3C%2FLINGO-BODY%3E%3CLINGO-LABS%20id%3D%22lingo-labs-817755%22%20slang%3D%22en-US%22%3E%3CLINGO-LABEL%3E2013%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3E2016%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3EPermissions%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3ESecurity%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3ESharePoint%20Server%3C%2FLINGO-LABEL%3E%3C%2FLINGO-LABS%3E%3CLINGO-SUB%20id%3D%22lingo-sub-818082%22%20slang%3D%22en-US%22%3ERe%3A%20Check%20user%20permissions%20for%20users%20in%20trusted%20domains%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-818082%22%20slang%3D%22en-US%22%3EIs%20this%20cross%20forest%3F%20Adding%20users%20in%20a%20cross%20forest%20scenario%20to%20a%20security%20group%20in%20the%20remote%20forest%20has%20never%20worked%20as%20the%20users%20are%20added%20to%20the%20remote%20forest%20as%20a%20foreign%20security%20principal%20which%20SharePoint%20cannot%20resolve.%3C%2FLINGO-BODY%3E%3CLINGO-SUB%20id%3D%22lingo-sub-818098%22%20slang%3D%22en-US%22%3ERe%3A%20Check%20user%20permissions%20for%20users%20in%20trusted%20domains%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-818098%22%20slang%3D%22en-US%22%3E%3CP%3E%3CA%20href%3D%22https%3A%2F%2Ftechcommunity.microsoft.com%2Ft5%2Fuser%2Fviewprofilepage%2Fuser-id%2F130%22%20target%3D%22_blank%22%3E%40Trevor%20Seward%3C%2FA%3EYes%2C%20it's%20cross%20forest.%20%3CSTRIKE%3EIt%20works%20in%20SharePoint%202013%20if%20the%20People%20Picker%20properties%26nbsp%3BSidHistorySafeMode%20and%20UseGlobalCatalog%20both%20are%20set%20to%20false%20(which%20is%20the%20default%20value).%3C%2FSTRIKE%3E%3C%2FP%3E%3CP%3E%3CFONT%3EIt%20works%20in%20SharePoint%202013%20because%20%3CFONT%3Eprincipal.GetAuthorizationGroups()%3C%2FFONT%3Eis%20always%20called%20when%20getting%20the%20token%20groups.%26nbsp%3B%3C%2FFONT%3E%3C%2FP%3E%3CP%3E%3CFONT%3EThe%20users%20are%20in%20the%20remote%20domain%2C%20and%20groups%20are%20in%20the%20local%20domain%20where%20SharePoint%20is%20installed.%3C%2FFONT%3E%3C%2FP%3E%3C%2FLINGO-BODY%3E
Highlighted
New Contributor

I have identified a change in how SharePoint handles the Check Permissions functionality from SharePoint 2013 to SharePoint 2016/2019. If you have users in domain A, and a SharePoint farm and security groups in domain B, SharePoint 2016/2019 will fail to show if a user has access to a site if he is granted access using security groups (from domain B).

This is caused by a change in the internal static method

 

Microsoft.SharePoint.Administration.Claims.SPClaimsAuthRoleProviderGetRolesForUserBestEffort(string username)

 

In SharePoint 2013 the implementation of this method makes a call to

 

System.DirectoryServices.AccountManagement.UserPrincipal()

 

In SharePoint 2016 and 2019 this method will never be called because of an enabled feature flag (AuthZenImprovedGetRolesForUserBestEffort) that now changes the implementation to only make a call to the method

 

Microsoft.SharePoint.Administration.Claims.SPClaimsAuthRoleProvider.GetTokenGroupsForUser(PrincipalContext principalContext, SPClaim entity, List<string> result)

 

The behavior in SharePoint 2016 and 2019 is the same as what would happen in SharePoint 2013 if the SPWebApplication PeoplePicker property SidHistorySafeMode or UseGlobalCatalog where set to true (both defaults to false).

 

My questions are:

Is this design change intentional, and if so, what is the reason behind that? Does it mean that it's not supported to have a SharePoint (2016/2019) farm installation that has users in a separate domain like mentioned above?

 

Is this a bug/oversight or something that can be changed so that it's possible to correctly identify permissions for users in a trusted domain?

 

Is there a workaround or alternate approach to successfully have the Check Permissions functionality work when having users in a separate trusted domain?


SP2013 code:

 

UserPrincipal principal = null;
try
{
    principal = UserPrincipal.FindByIdentity(principalContext, entity.Value);
}
catch
{
    principal = null;
}
if (principal != null)
{
    userSecurityIdentifier = principal.Sid;
    if (SidHistorySafeMode || UseGlobalCatalog)
    {
        GetTokenGroupsForUser(principalContext, principal, result, strDomain);
    }
    else
    {
        foreach (Principal principal2 in principal.GetAuthorizationGroups())
        {
            result.Add(SPClaimEncodingManager.EncodeClaimIntoFormsSuffix(SPClaimTypes.GroupSid, principal2.Sid.Value, SPClaimValueTypes.String, SPOriginalIssuers.Format(SPOriginalIssuerType.Windows)));
        }
    }
}

 

SP2019 code:

 

if (VariantConfiguration.IsGlobalExpFeatureToggleEnabled(ExpFeatureId.AuthZenImprovedGetRolesForUserBestEffort))
{
    GetTokenGroupsForUser(principalContext, entity, result);
}
else
{
    UserPrincipal principal = null;
    try
    {
        principal = UserPrincipal.FindByIdentity(principalContext, entity.Value);
    }
    catch
    {
        principal = null;
    }
    if (principal != null)
    {
        userSecurityIdentifier = principal.Sid;
        foreach (Principal principal2 in principal.GetAuthorizationGroups())
        {
            result.Add(SPClaimEncodingManager.EncodeClaimIntoFormsSuffix(SPClaimTypes.GroupSid, principal2.Sid.Value, SPClaimValueTypes.String, SPOriginalIssuers.Format(SPOriginalIssuerType.Windows)));
        }
    }
}

 

2 Replies
Highlighted
Is this cross forest? Adding users in a cross forest scenario to a security group in the remote forest has never worked as the users are added to the remote forest as a foreign security principal which SharePoint cannot resolve.
Highlighted

@Trevor SewardYes, it's cross forest. It works in SharePoint 2013 if the People Picker properties SidHistorySafeMode and UseGlobalCatalog both are set to false (which is the default value).

It works in SharePoint 2013 because principal.GetAuthorizationGroups() is always called when getting the token groups. 

The users are in the remote domain, and groups are in the local domain where SharePoint is installed.