none
Grant Permissions Dialog fails with Custom Claim Provider

    Question

  • Hi everyone.

    I've created a custom claims provider to provide claim resolution for SAML claims in the people picker.

    The claims are resolving as expected but the problem is that when you click Ok to add the resolved claim/user to SharePoint I get the following message.

    The user does not exist or is not unique.<nativehr>0x81020054</nativehr><nativestack></nativestack>

    [COMException (0x81020054): The user does not exist or is not unique.<nativehr>0x81020054</nativehr><nativestack></nativestack>] Microsoft.SharePoint.Library.SPRequestInternalClass.UpdateMembers(String bstrUrl, UInt32 dwObjectType, String bstrObjId, Guid& pguidScopeId, Int32 lGroupID, Int32 lGroupOwnerId, Object& pvarArrayAdd, Object& pvarArrayAddIds, Object& pvarArrayLoginsRemove, Object& pvarArrayIdsRemove, Boolean bRemoveFromCurrentScopeOnly, Boolean bSendEmail) +0 Microsoft.SharePoint.Library.SPRequest.UpdateMembers(String bstrUrl, UInt32 dwObjectType, String bstrObjId, Guid& pguidScopeId, Int32 lGroupID, Int32 lGroupOwnerId, Object& pvarArrayAdd, Object& pvarArrayAddIds, Object& pvarArrayLoginsRemove, Object& pvarArrayIdsRemove, Boolean bRemoveFromCurrentScopeOnly, Boolean bSendEmail) +240 [SPException: The user does not exist or is not unique.] Microsoft.SharePoint.SPGlobal.HandleComException(COMException comEx) +27257906 Microsoft.SharePoint.Library.SPRequest.UpdateMembers(String bstrUrl, UInt32 dwObjectType, String bstrObjId, Guid& pguidScopeId, Int32 lGroupID, Int32 lGroupOwnerId, Object& pvarArrayAdd, Object& pvarArrayAddIds, Object& pvarArrayLoginsRemove, Object& pvarArrayIdsRemove, Boolean bRemoveFromCurrentScopeOnly, Boolean bSendEmail) +27642598 Microsoft.SharePoint.SPUserCollection.UpdateMembers(Object objUpdateInfo, Object objAddIds, Object objRemoveLogins, Object objRemoveIds, Boolean fSendEmail) +560 Microsoft.SharePoint.SPUserCollection.AddCollection(SPUserInfo[] addUsersInfo, IEnumerable`1 addUsers) +1060 Microsoft.SharePoint.ApplicationPages.AclInv.BtnOK_Click(Object sender, EventArgs e) +631 System.Web.UI.WebControls.Button.OnClick(EventArgs e) +115 System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +140 System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +29 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2981

    I'm not sure what is causing this or where to look at solving it. Any ideas?

    Tuesday, March 22, 2011 5:59 PM

Answers

  • I was able to solve this.

    I had used this walkthrough to initially create my claims provider. http://msdn.microsoft.com/en-us/library/ff699494.aspx.  I then updated it to include queries against our ADFS 2.0 provider.

    Problem:

    The problem turned out to be that I had not correctly implemented user claims for the SharePoint group assignment screen.  

    In the initial case the SAML Token Issuer was using the default claims provider (check the SPTrustedIdentityTokenIssuer.ClaimProviderName property) the follow scenario occurred.

    1. The people picker resolved the query using my custom claim provider correctly.
    2. I selected the resolved people entity and selected ok.
    3. After clicking ok on the group assignment screen another request is issued to the claims providers to verify the person entity about to be assigned is resolved.
    4. My provider returned 0 results given that it was only expecting formroles requests.
    5. The default claims provider verified the request for the group assignment.

    This resulted in a resolved claim that was not the one I was expecting but rather from the default provider.  (This provider resolves everything to be true) The net result was that I thought my custom claims provider was working when in fact it was not.  

    After I changed the the claim provider the SAML token issuer was using via powershell I was receiving the error found in my initial post.

    Example

     

    $myProvider = Get-SPTrustedIdentityTokenIssuer -Name "My SAML Provider";
    $myProvider.ClaimProviderName = "MyClaimsProvider";
    $myProvider.Update();

    The scenario that was now happening is as follows.

    1. The people picker resolved the query using my custom claim provider correctly.
    2. I selected the resolved people entity and selected ok.
    3. After clicking ok on the group assignment screen another request is issued to the claims providers to verify the person entity about to be assigned is resolved.
    4. My provider returned 0 results given that it was only expecting formroles requests

     

    Setting the Login Provider to use my custom claims provider simply stopped step 5 from occuring causing the error "The user does not exist or is not unique".

    Solution:

    The solution here is to make sure if you use the above mentioned walkthrough to change the code sample below to recognize the types of claims you wish to resolve.  In the case of wanting to use this for assigning a claims user to a SharePoint group allow checking for the type "SPClaimEntityTypes.User".

     

    if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
      return;

    There are three methods in which this is pertinent.  Understanding what method is called for what purpose goes a long way to being able to understand and correctly write a custom claims provider.

    FillSearch
    Which is used why using the SharePoint people picker browse function.

    FillResolve(Uri context, string[] entityTypes, string resolveInput, List<Microsoft.SharePoint.WebControls.PickerEntity> resolved)
    Which is used when validating input typed into the people picker. 

    FillResolve(Uri context, string[] entityTypes, SPClaim resolveInput, List<Microsoft.SharePoint.WebControls.PickerEntity> resolved)
    Which is used after clicking ok on an SharePoint assignment screen. This method is expecting a single exact match to the item in the resolveInput parameter.


    • Marked as answer by Mark Zacharias Tuesday, March 29, 2011 6:56 PM
    • Edited by Mark Zacharias Wednesday, July 6, 2011 8:45 PM Clarified answer to question
    Tuesday, March 29, 2011 6:56 PM

All replies

  • I've found that it works as is as long as I don't set my SPTrustedLoginProvider.ClaimsProviderName to my custom claims provider.

    Has anyone been able to get that working?  The whole point of my custom claims provider is to replace the default provider and support search from the people picker.

    Wednesday, March 23, 2011 7:29 PM
  • I was able to solve this.

    I had used this walkthrough to initially create my claims provider. http://msdn.microsoft.com/en-us/library/ff699494.aspx.  I then updated it to include queries against our ADFS 2.0 provider.

    Problem:

    The problem turned out to be that I had not correctly implemented user claims for the SharePoint group assignment screen.  

    In the initial case the SAML Token Issuer was using the default claims provider (check the SPTrustedIdentityTokenIssuer.ClaimProviderName property) the follow scenario occurred.

    1. The people picker resolved the query using my custom claim provider correctly.
    2. I selected the resolved people entity and selected ok.
    3. After clicking ok on the group assignment screen another request is issued to the claims providers to verify the person entity about to be assigned is resolved.
    4. My provider returned 0 results given that it was only expecting formroles requests.
    5. The default claims provider verified the request for the group assignment.

    This resulted in a resolved claim that was not the one I was expecting but rather from the default provider.  (This provider resolves everything to be true) The net result was that I thought my custom claims provider was working when in fact it was not.  

    After I changed the the claim provider the SAML token issuer was using via powershell I was receiving the error found in my initial post.

    Example

     

    $myProvider = Get-SPTrustedIdentityTokenIssuer -Name "My SAML Provider";
    $myProvider.ClaimProviderName = "MyClaimsProvider";
    $myProvider.Update();

    The scenario that was now happening is as follows.

    1. The people picker resolved the query using my custom claim provider correctly.
    2. I selected the resolved people entity and selected ok.
    3. After clicking ok on the group assignment screen another request is issued to the claims providers to verify the person entity about to be assigned is resolved.
    4. My provider returned 0 results given that it was only expecting formroles requests

     

    Setting the Login Provider to use my custom claims provider simply stopped step 5 from occuring causing the error "The user does not exist or is not unique".

    Solution:

    The solution here is to make sure if you use the above mentioned walkthrough to change the code sample below to recognize the types of claims you wish to resolve.  In the case of wanting to use this for assigning a claims user to a SharePoint group allow checking for the type "SPClaimEntityTypes.User".

     

    if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
      return;

    There are three methods in which this is pertinent.  Understanding what method is called for what purpose goes a long way to being able to understand and correctly write a custom claims provider.

    FillSearch
    Which is used why using the SharePoint people picker browse function.

    FillResolve(Uri context, string[] entityTypes, string resolveInput, List<Microsoft.SharePoint.WebControls.PickerEntity> resolved)
    Which is used when validating input typed into the people picker. 

    FillResolve(Uri context, string[] entityTypes, SPClaim resolveInput, List<Microsoft.SharePoint.WebControls.PickerEntity> resolved)
    Which is used after clicking ok on an SharePoint assignment screen. This method is expecting a single exact match to the item in the resolveInput parameter.


    • Marked as answer by Mark Zacharias Tuesday, March 29, 2011 6:56 PM
    • Edited by Mark Zacharias Wednesday, July 6, 2011 8:45 PM Clarified answer to question
    Tuesday, March 29, 2011 6:56 PM
  • hi, Mark Zacharias.
    I encounter the exactly same problem. My SP web app uses a custom STS as identity provider. I developed a custom claim provider.

    If i don't misunderstand you. The solution to this problem is to change "if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole))
    " to "if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))" in methods FillResolve, FillSearch and FillHierarchy (my claim provider doesn't support hierarchy so there is no need to change this method)

    But my code is just "if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))", the problem still there.


    Another question: you said
    "The whole point of my custom claims provider is to replace the default provider and support search from the people picker." and "Setting the Login Provider to use my custom claims provider", can you explain the meaning of this?
    Since the custom STS itself is a claim provider but it doesn't make any sense when searching users,
    my following post is to find a way to remove it. It seems that it can be replaced by the custom claim provider, right? how to replace it?
    http://social.technet.microsoft.com/Forums/en-CA/sharepoint2010general/thread/23ad2a9e-6517-4505-ba5b-d4cbf3c44dec

    thank you for your help


    Wednesday, July 6, 2011 12:18 PM
  • It's been a while since I wrote that post and after reading it I've tried to clarify my answer a bit. (See edited post above).

    The error I had initially posted about comes from one of the various calls made to the claims provider.  One of those calls is failing to resolve the request that is being made.  Depending on what stage the request is different and handled by a different method.  (See the FillSearch and FillResolve method explanations above).  You'll need to ensure that the requests are being handled successfully.  In my case it was caused by my custom claim provider failing to properly handle the expected entity type.

    I posted in your other thread as well, basically you need to register your custom claims provider as the one used by the token issuer.  The token issuer is not in and of itself a claim provider but instead references a claim provider is uses for claim verification requests.

    Wednesday, July 6, 2011 8:52 PM
  • thank you for your explaination,  Mark Zacharias


    By running the following I replaced the STS's default claim provider with my custom claim provider.

    $yourProvider = Get-SPTrustedIdentityTokenIssuer -Name "Your SAML Provider";

    $yourProvider.ClaimProviderName = "YourClaimsProvider";

    $yourProvider.Update();

    But i still can't find the cause and a solution of the problem "The user does not exist or is not unique".

    Thursday, July 7, 2011 12:05 PM
  •  One of those calls is failing to resolve the request that is being made.  Depending on what stage the request is different and handled by a different method.  (See the FillSearch and FillResolve method explanations above).  You'll need to ensure that the requests are being handled successfully.  

    Make sure you under stand what method is called at which point and what the expected output is.  Then double triple and quadruple check all your methods to ensure that they are resolving the information as SharePoint is expecting it.
    Remember claims providers are a bit tricky at first, but once you understand the claim life-cycle it's not so bad.
    Thursday, July 7, 2011 4:56 PM
  • I know the importance of the claim life-cycle.  There are lots of overridable methods in SPClaimProvider class. I want to know what those methods are expected to do and when they are called by sharepoint.

    But i can't find books or articles to explain those in details. I find lots of "HOW-TO" articles such as http://msdn.microsoft.com/en-us/library/ff699494.aspx, but they don't explain "WHY".

    In your post above, do you mean the problem is related to the implementation of FillSearch and FillResolve methods.

    Here is my claim provider, does it has any implementation problem?

     public class UserNameClaimProvider : SPClaimProvider
    
     {
    
      internal const string ClaimProviderDisplayName = "SanTongYiSTS";
    
    
    
      public UserNameClaimProvider(string displayName)
    
       : base(displayName)
    
      {
    
       
    
      }
    
    
    
      protected override void FillClaimTypes(List<string> claimTypes)
    
      {
    
       if (claimTypes == null) throw new ArgumentException("claimTypes");
    
    
    
       claimTypes.Add(UserAccountClaimType);
    
      }
    
    
    
      protected override void FillClaimValueTypes(List<string> claimValueTypes)
    
      {
    
       if (claimValueTypes == null) throw new ArgumentException("claimValueTypes");
    
    
    
       claimValueTypes.Add(UserAccountClaimValueType);
    
      }
    
    
    
      protected override void FillClaimsForEntity(Uri context, SPClaim entity, List<SPClaim> claims)
    
      {
    
       throw new NotImplementedException();
    
      }
    
    
    
      protected override void FillEntityTypes(List<string> entityTypes)
    
      {
    
       entityTypes.Add(SPClaimEntityTypes.User);
    
      }
    
    
    
      protected override void FillHierarchy(Uri context, string[] entityTypes, string hierarchyNodeID, int numberOfLevels, Microsoft.SharePoint.WebControls.SPProviderHierarchyTree hierarchy)
    
      {
    
       throw new NotImplementedException();
    
      }
    
    
    
      protected override void FillResolve(Uri context, string[] entityTypes, SPClaim resolveInput, List<Microsoft.SharePoint.WebControls.PickerEntity> resolved)
    
      {
    
       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))
    
        return;
    
    
    
       PickerEntity pickerEntity = base.CreatePickerEntity();
    
       pickerEntity.Claim = base.CreateClaim(UserAccountClaimType, resolveInput.Value, resolveInput.ValueType);
    
       pickerEntity.Description = resolveInput.Value;
    
       pickerEntity.DisplayText = resolveInput.Value;
    
       pickerEntity.EntityType = SPClaimEntityTypes.User;
    
       pickerEntity.IsResolved = true;
    
    
    
       resolved.Add(pickerEntity);
    
      }
    
    
    
      protected override void FillResolve(Uri context, string[] entityTypes, string resolveInput, List<Microsoft.SharePoint.WebControls.PickerEntity> resolved)
    
      {
    
       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))
    
        return;
    
    
    
       PickerEntity pickerEntity = base.CreatePickerEntity();
    
       pickerEntity.Claim = base.CreateClaim(UserAccountClaimType, resolveInput,UserAccountClaimValueType);
    
       pickerEntity.Description = resolveInput;
    
       pickerEntity.DisplayText = resolveInput;
    
       pickerEntity.EntityType = SPClaimEntityTypes.User;
    
       pickerEntity.IsResolved = true;
    
    
    
       resolved.Add(pickerEntity);
    
      }
    
    
    
      protected override void FillSchema(Microsoft.SharePoint.WebControls.SPProviderSchema schema)
    
      {
    
       schema.AddSchemaElement(new SPSchemaElement(
    
              PeopleEditorEntityDataKeys.DisplayName,
    
              "Display Name",
    
              SPSchemaElementType.Both));
    
      }
    
    
    
      protected override void FillSearch(Uri context, string[] entityTypes, string searchPattern, string hierarchyNodeID, int maxCount, 
    
       Microsoft.SharePoint.WebControls.SPProviderHierarchyTree searchTree)
    
      {   
    
    
    
       if (!EntityTypesContain(entityTypes, SPClaimEntityTypes.User))
    
        return;
    
       OguReaderClient oguReader = new OguReaderClient();
    
       XElement account = oguReader.GetObjectsDetail(ConfigurationManager.AppSettings["SanTongYiViewCode"], ViewCategory.ViewCode, 
    
          string.Format("{0}&{1}", searchPattern, ConfigurationManager.AppSettings["SanTongYiAccountSearchType"]),
    
          ObjectCategory.UserIdentity, string.Empty, OrganizationCategory.None, string.Empty);
    
       if (!string.IsNullOrEmpty(account.Value))
    
       {
    
        searchTree.AddEntity(this.CreatePickerEntityForUserName(searchPattern));
    
       }
    
    
    
      }
    
      private PickerEntity CreatePickerEntityForUserName(string userAccount)
    
      {
    
       //Create the Picker Entity
    
       PickerEntity entity = CreatePickerEntity();
    
       entity.Claim = CreateClaim(UserAccountClaimType, userAccount, UserAccountClaimValueType);
    
       entity.Description = ProviderDisplayName + ":" + userAccount;
    
       entity.DisplayText = userAccount;
    
       entity.EntityType = SPClaimEntityTypes.User;
    
       entity.IsResolved = true;
    
       entity.EntityGroupName = "SanTongYiAccount";
    
       entity.EntityData["SanTongYiAccount"] = userAccount;
    
       return entity;
    
      }
    
      
    
      private static string UserAccountClaimType
    
      {
    
       get
    
       {
    
        //return "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
    
        return "http://www.microsoft.com/identity/claims/username";
    
       }
    
      }
    
    
    
      private static string UserAccountClaimValueType
    
      {
    
       get
    
       {
    
        return Microsoft.IdentityModel.Claims.ClaimValueTypes.String;
    
       }
    
      }
    
    
    
      internal static string ProviderDisplayName
    
      {
    
       get
    
       {
    
        return "SanTongYiUserAccount";
    
       }
    
      }
    
      internal static string ProviderInternalName
    
      {
    
       get
    
       {
    
        return "SanTongYiUserName";
    
       }
    
      }
    
      public override string Name
    
      {
    
       get { return ClaimProviderDisplayName ; }
    
      }
    
    
    
      public override bool SupportsEntityInformation
    
      {
    
       get { return false; }
    
      }
    
    
    
      public override bool SupportsHierarchy
    
      {
    
       get { return false; }
    
      }
    
    
    
      public override bool SupportsResolve
    
      {
    
       get { return true; }
    
      }
    
    
    
      public override bool SupportsSearch
    
      {
    
       get { return true; }
    
      }
    
     }
    
    
    
    

     


    Friday, July 8, 2011 1:03 AM
  • I found another clue to this problem.  I changed the UserAccountClaimType from http://www.microsoft.com/identity/claims/username to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name,

      private static string UserAccountClaimType

      {
       get
       {
         return "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
        //return "http://www.microsoft.com/identity/claims/username";
       }
      }

    In the "Select People and Groups" dialog, when i selected a user and click "Add->" and then click "OK", the FillResolve method using SPClaim arguement is not called.

    Besides, when UserAccountClaimType  is set to  http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name. My Claim Provider is exactly the same as described in http://stackoverflow.com/questions/5505250/sharepoint-2010-custom-claims-provider. when UserAccountClaimType   is not set to http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name (all other value is ok), the "The user does not exist or is not unique" error occurs.


    Claim Providers are indeed very tricky.
    Monday, July 11, 2011 2:13 AM
  • Hi Mark,

    In my case, When I add any role on grant permission page, FillResolve() method with SPClaim parameter called twice when I hit on OK button. Because of this, it fails with the same error you have posted above. If I skip second call through debugging, it adds the role successfully. Can you say why is it called twice when adding role.

    In case of adding user, it is called once and add the user successfully.

    Any Idea?

    Thursday, October 13, 2011 8:47 AM
  • It's been a while since I've dug around in reflector for this stuff, but if I remember correctly Fill Resolve is being called twice because...

    1. It is called from the people picker on the button click to resolve the user that has been selected.
    2. It is called a 2nd time by the add user to group code the people picker is sending the resolved user to.

    I'm not sure why your picker might be failing but hopefully understanding the life cycle can help you debug what you are seeing. 

    Tuesday, November 8, 2011 10:07 PM