dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.82k stars 4.61k forks source link

System.DirectoryServices.AccountManagement UserProxyPrincipal not working as expected #40972

Open zdhayaz opened 4 years ago

zdhayaz commented 4 years ago

Description

We are trying to extract and display a UserProxyPrincipal from AD-LDS server onto a web application. Ideally a userProxyFull object from ADLDS/ ADAM with System.DirectoryServices.AccountManagement library. We can get the information as we expected with DirectorySearcher but extended PrincipalContext.FindByIdentity does not work.

Following works

   DirectoryEntry dirEntry = new DirectoryEntry(@"LDAP://" + server + "/" + ou + "");
   dirEntry.AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.SecureSocketsLayer;
   DirectorySearcher dirSearch = new DirectorySearcher(dirEntry);
   dirSearch.Filter = $"(cn={searchUser})";
   dirEntry.Username = usr;
   dirEntry.Password = pwd;
   dirSearch.PageSize = 100;
   SearchResult srhResult = dirSearch.FindOne();
   dirSearch.Dispose();

Following Doesn't

   using (var context = new PrincipalContext(ContextType.ApplicationDirectory, server, ou, ContextOptions.Negotiate | ContextOptions.SecureSocketLayer, usr, pwd))
            {
                //var response = context.ValidateCredentials(usr, pwd); //<--- fails here
                using (var user = UserProxyPrincipal.FindByIdentity(context, searchUser)) //<--- fails here with error "The server is not operational"
                {
                    if (user != null)
                    {
                    }
                }
            }

//Following is the code I used for extending UserPrincipal class to retrive userProxyFull objects from AD LDS/ADAM.

namespace System.DirectoryServices.AccountManagement
{
    [DirectoryRdnPrefix("CN")]
    [DirectoryObjectClass("userProxyFull")]
    public class UserProxyPrincipal : UserPrincipal
    {
        public UserProxyPrincipal(PrincipalContext context)
            : base(context) { }
        public UserProxyPrincipal(PrincipalContext context, string samAccountName, string password, bool enabled)
            : base(context, samAccountName, password, enabled) { }

        public static new UserProxyPrincipal FindByIdentity(PrincipalContext context, string identityValue)
        { 
            var vvv = FindByIdentityWithType(context, typeof(UserProxyPrincipal), identityValue);
            return (UserProxyPrincipal)vvv;
        }
        public static new UserProxyPrincipal FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
        {
            return (UserProxyPrincipal)FindByIdentityWithType(context, typeof(UserProxyPrincipal), identityType, identityValue);
        }

        [DirectoryProperty("objectSid")]
        public string ObjectSid
        {
            get
            {
                var values = ExtensionGet("objectSid");
                return ((values != null) && (values.Length > 0) ? values[0].ToString() : null);
            }
            set { ExtensionSet("objectSid", value); }
        }

        [DirectoryProperty("name")]
        public new string Name
        {
            get
            {
                var values = ExtensionGet("name");
                return ((values != null) && (values.Length > 0) ? values[0].ToString() : null);
            }
            set { ExtensionSet("name", value); }
        }
    }
}

Configuration

Regression?

Other information

DirectorySearcher works because it considers the load balanced alias as the actual server name. Where as System.DirectoryServices.AccountManagement libraries ADUtils.cs tries and gets the actual server name, it retrieves the actual server name and tried to connect to i but unfortunately the direct server access is blocked and hence the issue based on my understanding. I inspected the network traffic and DirectoryEntry never connects to server directly it honours the clustered alias name but PrincipalContext.FindUserbyIdentity doesnt.

internal static string GetServerName(DirectoryEntry de) { UnsafeNativeMethods.IAdsObjectOptions objOptions = (UnsafeNativeMethods.IAdsObjectOptions)de.NativeObject; return (string)objOptions.GetOption(0 /* == ADS_OPTION_SERVERNAME */); }

ericstj commented 3 years ago

triage: Not a regression, behavior exists eve in .NETFramework. Moving out.

@tquerec please have a look.