Second Life of a Hungarian SharePoint Geek

March 5, 2014

“The user does not exist or is not unique” Error When a User is Disabled in Active Directory

Filed under: Active Directory, Reflection, SP 2010 — Tags: , , — Peter Holpar @ 22:54

At one of our customers there is an Active Directory that contains several domains. There are overlapping user accounts in these domains, like DomainA\User1 and DomainB\User1, often one of these accounts is disabled, while the same account in the other domain is active.

Recently I worked with a list, where I chose a user in People picker without problems, however, when saving the list item, I received an error:

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

image

The full stack trace:

[COMException (0x81020054): The user does not exist or is not unique.<nativehr>0x81020054</nativehr><nativestack></nativestack>]
   Microsoft.SharePoint.Library.SPRequestInternalClass.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback) +0
   Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback) +406

[SPException: The user does not exist or is not unique.]
   Microsoft.SharePoint.SPGlobal.HandleComException(COMException comEx) +27609794
   Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback) +28003791
   Microsoft.SharePoint.SPListItem.AddOrUpdateItem(Boolean bAdd, Boolean bSystem, Boolean bPreserveItemVersion, Boolean bNoVersion, Boolean bMigration, Boolean bPublish, Boolean bCheckOut, Boolean bCheckin, Guid newGuidOnAdd, Int32& ulID, Object& objAttachmentNames, Object& objAttachmentContents, Boolean suppressAfterEvents, String filename) +26729773
   Microsoft.SharePoint.SPListItem.UpdateInternal(Boolean bSystem, Boolean bPreserveItemVersion, Guid newGuidOnAdd, Boolean bMigration, Boolean bPublish, Boolean bNoVersion, Boolean bCheckOut, Boolean bCheckin, Boolean suppressAfterEvents, String filename) +26726382
   Microsoft.SharePoint.SPListItem.Update() +161
   Microsoft.SharePoint.WebControls.SaveButton.SaveItem(SPContext itemContext, Boolean uploadMode, String checkInComment) +20288612
   Microsoft.SharePoint.WebControls.SaveButton.OnBubbleEvent(Object source, EventArgs e) +1315
   System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +70
   System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +29
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2981

A similar error can we reproduce when selecting a user in People picker, then before save, we set the account in AD as disabled:

image

When saving the item, the same “The user does not exist or is not unique” exception is thrown as long the user was not yet available in the user list of SharePoint. If the user was already former created in the SharePoint user list, there was no error, even if the user was disabled in AD.

However, in our case, it was a bit more complicated. When I searched the user in People picker, only one of the accounts was shown up, the one that was not disabled (in this case, from DomainA). The disabled account from the other domain was not included in the People picker. So far, so good.

image

However, after I selected the user and closed the People picker by pressing OK, the other, disabled user (from DomainB) was set in the field, and caused the issue at save.

image

When I set the account by explicitly specifying the domain (like DomainA\testuser) I hade no such problems.

After a user from DomainB was resolved by People picker, both accounts was shown up in the result list (at least, for a while).

I’ve created a few network traffic capture using Wireshark, and found that the LDAP queries again the AD were correct, searching only accounts that are not disabled (see condition (!(userAccountControl:1.2.840.113556.1.4.803:=2) below).

(&(&(&(objectCategory=person)(objectClass=user))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))(|(|(|(|(|(|(|(name=testuser*)(displayName=testuser*))(cn=testuser*))(mail=testuser*))(sn=testuser*))(SamAccountName=testuser*))

The response from AD was OK as well, including only the active user from DomainA.

Using SQL Server Profiler I found queries, like the ones below:

exec sp_executesql N’DECLARE @DocParentIdForRF uniqueidentifier SELECT TOP 1 @DocParentIdForRF = Docs.Id FROM Docs WHERE Docs.SiteId = @SITEID AND Docs.DirName = @FDN AND Docs.LeafName = @FLN;   SELECT TOP(@NUMROWS) t1.[Type] AS c0, UserData.[tp_ContentTypeId], UserData.[nvarchar10], UserData.[tp_ID], UserData.[tp_CopySource], UserData.[tp_Version], t1.[Id] AS c4, UserData.[nvarchar4], UserData.[nvarchar9], UserData.[nvarchar5], UserData.[tp_Created], t1.[SortBehavior] AS c1, UserData.[tp_HasCopyDestinations], UserData.[nvarchar1], UserData.[tp_ModerationStatus], UserData.[tp_Level], UserData.[nvarchar6], t1.[MetaInfo] AS c3, t1.[ScopeId] AS c5, UserData.[tp_Modified], CASE WHEN DATALENGTH(t1.DirName) = 0 THEN t1.LeafName WHEN DATALENGTH(t1.LeafName) = 0 THEN t1.DirName ELSE t1.DirName + N”/” + t1.LeafName END  AS c2, UserData.[nvarchar3] FROM UserData INNER MERGE JOIN Docs AS t1 WITH(NOLOCK) ON (UserData.[tp_RowOrdinal] = 0) AND (t1.SiteId=UserData.tp_SiteId) AND (t1.SiteId = @SITEID) AND (t1.ParentId = UserData.tp_ParentId) AND (t1.Id = UserData.tp_DocId) AND ( (UserData.tp_Level = 1) ) AND (t1.Level = UserData.tp_Level) AND (t1.IsCurrentVersion = 1) AND (t1.Level = 1 OR t1.Level =  2) WHERE ( (UserData.tp_Level = 1) ) AND (UserData.tp_SiteId=@SITEID AND (UserData.tp_ParentId=@DocParentIdForRF)) AND (UserData.tp_RowOrdinal=0) AND ((UserData.[bit3]=0 ) AND ((UserData.[tp_ContentTypeId] = @L2IMG) AND (((UserData.[nvarchar3] LIKE @L3TXP) OR (UserData.[nvarchar1] LIKE @L4TXP)) OR ((UserData.[nvarchar4] LIKE @L4TXP) OR (UserData.[nvarchar5] LIKE @L3TXP)))) AND t1.SiteId=@SITEID AND (t1.ParentId=@DocParentIdForRF)) ORDER BY UserData.[nvarchar3]  ASC ,UserData.[tp_ID]  ASC  OPTION (FORCE ORDER, MAXDOP 1)’,N’@LFFP uniqueidentifier,@SITEID uniqueidentifier,@L2IMG varbinary(8000),@L3TXP nvarchar(4000),@L4TXP nvarchar(4000),@FDN nvarchar(4000),@FLN nvarchar(4000),@NUMROWS bigint,@RequestGuid uniqueidentifier’,@LFFP=’00000000-0000-0000-0000-000000000000′,@SITEID=’94053632-22BC-4CEA-A146-8EC049097DA3′,@L2IMG=0x010A00980232D0CB4C3C41A835ED215B890946,@L3TXP=N’%DomainA\testuser%’,@L4TXP=N’DomainA\testuser%’,@FDN=N’my/_catalogs’,@FLN=N’users’,@NUMROWS=12,@RequestGuid=’5DBABDC5-3182-4866-8FBC-0E9244FE6733′

and

exec proc_SecResolvePrincipal @SiteId=’94053632-22BC-4CEA-A146-8EC049097DA3′,@Input=N’DomainA\testuser’,@InputIsEmailOnly=0,@AccountName=N’DomainA\testuser’,@DLAlias=NULL,@DLAliasServerAddress=NULL,@SearchScope=1,@RequestGuid=’5DBABDC5-3182-4866-8FBC-0E9244FE6733′

While the user did not yet exist in the SharePoint site, neither of these queries had a result.

Using Reflector I found that the following classes and methods have significant roles in the user login name resolution:

Microsoft.SharePoint.Utilities.SPUtility class, ResolvePrincipalInternal, ResolveWindowsPrincipal, SearchAgainstAD methods
Microsoft.SharePoint.WebControls.PeopleEditor class, GetAccountFromSid method
Microsoft.SharePoint.Utilities.SPActiveDirectoryPrincipalBySIDResolver class, ResolvePrincipal method
Microsoft.SharePoint.Utilities.SPUserUtility class, ResolveAgainstUserInfoList method
System.Security.Principal.NTAccount class, Translate and TranslateToSids methods

There are a few approach to resolve the user via the methods above:

SPPrincipalInfo pi = SPUtility.ResolveWindowsPrincipal(web.Site.WebApplication, "testuser", SPPrincipalType.User, false);

or

SecurityIdentifier sid = (SecurityIdentifier)new NTAccount("testuser").Translate(typeof(SecurityIdentifier));

or using Reflection through the internal static GetAccountFromSid method of the PeopleEditor class:

  1. byte[] binaryForm = null;
  2. string ntAccountName = null;
  3. string domainName = null;
  4. string userName = null;
  5.  
  6. string lookUpName = "testuser";
  7. Console.WriteLine("Account name to be resolved: {0}", lookUpName);
  8.  
  9. SecurityIdentifier sid = (SecurityIdentifier)new NTAccount(lookUpName).Translate(typeof(SecurityIdentifier));
  10. binaryForm = new byte[sid.BinaryLength];
  11. sid.GetBinaryForm(binaryForm, 0);
  12.  
  13. Console.WriteLine("{0} (Original SID)", sid);
  14.  
  15. // call internal static method PeopleEditor.GetAccountFromSid
  16. object sidTypeInvalid = null;
  17. // sidTypeInvalid = SPAdvApi32.SID_NAME_USE.SidTypeInvalid;
  18. // hack to get the Microsoft.SharePoint assembly
  19. Assembly spAssembly = typeof(SPWeb).Assembly;
  20.  
  21. // get the internal enum type from the internal class
  22. Type sidNameUseType = spAssembly.GetType("Microsoft.SharePoint.Win32.SPAdvApi32+SID_NAME_USE");
  23. FieldInfo fi_SidTypeInvalid = sidNameUseType.GetField("SidTypeInvalid", BindingFlags.Public | BindingFlags.Static);
  24. sidTypeInvalid = fi_SidTypeInvalid.GetValue(null);
  25. MethodInfo mi_GetAccountFromSid = typeof(PeopleEditor).GetMethod("GetAccountFromSid", BindingFlags.NonPublic | BindingFlags.Static);
  26. ntAccountName = mi_GetAccountFromSid.Invoke(null, new object[] { binaryForm, userName, domainName, sidTypeInvalid }) as string;
  27. //pe.GetAccountFromSid(binaryForm, out userName, out domainName, out sidUse)
  28. SecurityIdentifier sid2 = (SecurityIdentifier)new NTAccount(ntAccountName).Translate(typeof(SecurityIdentifier));
  29. Console.WriteLine("Account name found: {0}", ntAccountName);
  30. Console.WriteLine("{0} (SID after GetAccountFromSid)", sid2);

We can use the public instance methods of the PeopleEditor class from a console application as well, however, in this case we have to inject a dummy HTTP context first.

  1. // inject fake context
  2. HttpRequest request = new HttpRequest(string.Empty, web.Url, string.Empty);
  3. HttpResponse response = new HttpResponse(new System.IO.StreamWriter(new System.IO.MemoryStream()));
  4. HttpContext dummyContext = new HttpContext(request, response);
  5. dummyContext.Items["HttpHandlerSPWeb"] = web;
  6. HttpContext.Current = dummyContext;
  7. PeopleEditor pe = new PeopleEditor();
  8.  
  9. // method 1
  10. ArrayList resolvedAccounts = pe.ResolveAccountsByArrKeys(new string[] { "testuser" });
  11.  
  12. // method 2
  13. PickerEntity entity = new PickerEntity();
  14. entity.Key = "testuser";
  15. var entity2 = pe.ValidateEntity(entity);

I found that the source of the issue might be the Translate method of the NTAccount class. If I call this method first (or after a long while) with the account name (like ‘testuser’), the SID of the disabled account is returned. When calling the method with the full login name (like ‘DomainA\testuser’ or ‘DomainB\testuser’), the SID of the corresponding user is returned. However, when we call the method with the account name (like ‘testuser’) just after we called the method with the full login name (like ‘DomainA\testuser’), the SID of the active user is returned. It seems that there is a sort of caching implemented somewhere in these objects, but since they are rather complex, including calls into unmanaged code, I was not (yet) able to resolve the issue of user name resolution.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: