Strange intermittent error handling error when calling UserPrinciapl.GetGroups in System.DirectoryServices.AccountManagement

Background

We have an asp.net 4.0 web application written in C # that calls a .net 3.5 web service written in C #. The user ID is transmitted to the web service and returns a list of data depending on the groups of active directories to which the user belongs.

The web service uses the .net 3.5 version of System.DirectoryServices.AccountManagement to obtain the Sids of the groups to which the user belongs.

The call to UserPrincipal.GetGroups is interrupted with the error below. Between the meetings there were very long periods of time, but when this happened, it was repeated for several minutes. This issue has occurred for different AD users.

The stack trace of this exception made no sense to us. We spent a lot of time analyzing Microsoft AD code in Reflector / ILSpy, but could not go beyond the call to IADsPathName.Retrieve.

An exception

System.NotSupportedException: Specified method is not supported. at System.Web.HttpResponseStream.get_Position() at System.Drawing.UnsafeNativeMethods.ComStreamFromDataStream.Seek(Int64 offset, Int32 origin) at System.DirectoryServices.AccountManagement.UnsafeNativeMethods.IADsPathname.Retrieve(Int32 lnFormatType) at System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo() at System.DirectoryServices.AccountManagement.ADStoreCtx.get_DnsForestName() at System.DirectoryServices.AccountManagement.ADStoreCtx.GetGroupsMemberOf(Principal p) at System.DirectoryServices.AccountManagement.Principal.GetGroupsHelper() at System.DirectoryServices.AccountManagement.Principal.GetGroups() at Data.SoftwarePublishingItemData.GetSids(String requestedForUserId) at Data.SoftwarePublishingItemData.GetSoftwarePublishingItems(IDatabaseContext dbContext, GetSoftwarePublishingItemsSettings settings, XBXmlDocument parameters) at Web.GetSoftwarePublishingItems.GetFlexiFieldData(String xml) 

Code to play

Please note that the CauseNotSupportedException method imitates code that does not work in our application, but in code somewhere else in the environment in which we do not control.

 class Program { static void Main(string[] args) { CauseNotSupportedException(); string samAccountName = "domain.user"; using (var principalContext = new PrincipalContext(ContextType.Domain)) { using (var userPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, samAccountName)) { if (userPrincipal == null) throw new ActiveDirectoryObjectNotFoundException(); using (var groups = userPrincipal.GetGroups()) { foreach (GroupPrincipal group in groups) { Console.WriteLine(group.Sid); } } } } } public static void CauseNotSupportedException() { using (var b = new Bitmap(500, 500, PixelFormat.Format32bppArgb)) { b.Save(new FakeStream(), ImageFormat.Png); } } } 

Stream implementation to simulate HttpResponseStream behavior

 public class FakeStream : Stream { public override bool CanRead { get { return false; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return true; } } public override void Flush() { } public override long Length { get { throw new NotSupportedException("No Seek"); } } public override long Position { get { throw new NotSupportedException("No Seek"); } set { throw new NotSupportedException("No Seek"); } } public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException("Write only stream"); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("net_noseek"); } public override void SetLength(long value) { } public override void Write(byte[] buffer, int offset, int count) { } } 

Questions

  • If you run the example above, an error that occurs in the CauseNotSupportedException method is raised in a GetGroups call. How can it be? Any theories or further understanding will be appreciated.
  • Any suggestions for further study?
  • Any better suggestions than the trick of exception and retry? This is our current job.

Thanks.

Explanation

I'm not sure how much I was in my explanation, so here are some clarifications. Firstly, I am pleased with the active directory that Sids receives. It does what I want it to be done, and I don't think the problem is with this as such. The real problem is that when an error occurs in another unrelated code (this is not in our application), the error manifests itself in a call to GetGroups, therefore, a strange stack trace with the error that originally occurred in System.Web.HttpResponseStream.get_Position (). In the sample application, a NotSupportedException occurs in CauseNotSupportedException, but the code does not break there, it is interrupted when GetGroups is called. If you comment on CauseNotSupportedException () in the sample application, an error never occurs.

I don’t understand how this can happen.

+6
source share
2 answers

After increasing support, Microsoft has released a hotfix for this issue. See the link below.

Specified reason: "This problem occurs because the System.DirectoryServices.AccountManagement namespace is a thin shell for Active Directory Active Directory (ADSI) native service interfaces. The IErrorInfo interface implemented by the IADsPathName interface responds to exceptions that ADSI does not throw "ADSI exception on the stack, the IErrorInfo interface throws an exception that is at the top of the stack, even if the exception is handled by another handler in the application."

http://support.microsoft.com/kb/2683913

Thanks to those who suggested the suggestions.

+4
source

If you are using .NET 3.5 or higher, you can use the new System.DirectoryServices.AccountManagement (S.DS.AM) namespace, which makes this a lot easier than before.

Read more here: [Managing directory security principles in the .NET Framework 3.5] [1]

Basically, you need to have a “main context” (usually your domain), a user principle, and then you easily get its groups:

 public List<GroupPrincipal> GetGroups(string userName) { List<GroupPrincipal> result = new List<GroupPrincipal>(); // establish domain context PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain); // find your user UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username); // if found - grab its groups if(user != null) { PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups(); // iterate over all groups foreach(Principal p in groups) { // make sure to add only group principals or change this to add to a list or varible if needed. if(p is GroupPrincipal) { result.Add(p); } } } return result; } 

To access certain properties that are not displayed on the UserPrincipal object, you need to search in the underlying DirectoryEntry :

 public string GetDepartment(Principal principal) { string result = string.Empty; DirectoryEntry de = (principal.GetUnderlyingObject() as DirectoryEntry); if (de != null) { if (de.Properties.Contains("samAccountName")) { result = de.Properties["samAccountName"][0].ToString(); } } return result; } //Change this Method to fit what ever your needs desire.. public string GetDepartment(string username) { string result = string.Empty; // if you do repeated domain access, you might want to do this *once* outside this method, // and pass it in as a second parameter! PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain); // find the user UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username); // if user is found if(user != null) { // get DirectoryEntry underlying it DirectoryEntry de = (user.GetUnderlyingObject() as DirectoryEntry); if (de != null) { if (de.Properties.Contains("department")) { result = de.Properties["department"][0].ToString(); } } } return result; } 
0
source

Source: https://habr.com/ru/post/905104/


All Articles