How would I validate a Username/Password using System.DirectoryServices.Protocol?
Asked Answered
A

2

5

First, I cannot use Active Directory, so I cannot use System.DirectoryServices directly. This will be a PC sending a query to a Novell network where only System.DirectoryServices.Protocol is supported.

I am pretty sure that I am down to needing to provide the proper SearchRequest.

This is what I have so far:

private static String _certificatePath;
private static String _server;

private static SearchResponse Query(String user, String pwd, out String error)
{
    SearchResponse result = null;
    error = String.Empty;
    if (File.Exists(_certificatePath))
    {
        var identifier = new LdapDirectoryIdentifier(_server, false, false);
        try
        {
            using (var connection = new LdapConnection(identifier))
            {
                connection.SessionOptions.ProtocolVersion = 3;
                var cert = new X509Certificate();
                cert.Import(_certificatePath, null, X509KeyStorageFlags.DefaultKeySet);
                connection.ClientCertificates.Add(cert);
                connection.AuthType = AuthType.External;
                connection.AutoBind = false;
                var request = new SearchRequest()
                {
                    DistinguishedName = user, //Find this person
                    Filter = "(objectClass=*)", //The type of entry we are looking for
                    Scope = System.DirectoryServices.Protocols.SearchScope.Subtree, //We want all entries below this ou
                };
                result = (SearchResponse)connection.SendRequest(request); //Run the query and get results
            }
        } catch (Exception err)
        {
            error = String.Format("SDSP::Query {0}: {1}", err.GetType(), err.Message);
        }
    }
    else
    {
        error = "The system cannot find the Cryptography Certificate at the path specified in the Application Configuration file.";
    }
    return result;
}

How do I create a SearchRequest to validate a user / pwd combination?

var request = new SearchRequest()
{
    DistinguishedName = user, //Find this person
    Filter = "(objectClass=*)", //The type of entry we are looking for
    Scope = System.DirectoryServices.Protocols.SearchScope.Subtree, //We want all entries below this ou
};
Auxiliaries answered 10/1, 2018 at 22:41 Comment(6)
Same question here. Did you realize how to do it?Acoustic
@Acoustic - I did not, but thanks for the reminder. I can post a bounty for this now.Auxiliaries
I have the exact same situation and I already spent couples of days trying to validate user/password pair. In my case, LDAP is configured to allow binding to just one user (an admin) so the only way that I have to validate user/password is binding with this "admin" and then searching and comparing some attribute or something like that. Everywere on internet says I have to compare "userPassowrd" attribute, but apparently that attribute doesn't exists in this LDAP configuration. I'm quite lost.Acoustic
@Acoustic - try finding a way to install a certificate. It will be something that comes from Novell (if you are trying to access the Novell server). Once that certificate is loaded (like I did in my example above), you do not need to log in with the "admin" account.Auxiliaries
I did it and it doesn't works, but I'm suspecting my case it's related to a feature turned off. Take a look to my comment on AsifAli72090 answer...Acoustic
networkCredential object seems to fit the bill, unless im mis steak en. msdn.microsoft.com/en-us/library/bb332056.aspxHenderson
E
2

On Windows

You can append ContextOptions.Negotiate parameter for ValidateCredentials (Username and Password).

const int ldapErrorInvalidCredentials = 0x31;

const string server = "sd.example.com:636";
const string domain = "sd.example.com";

try
{
    using (var ldapConnection = new LdapConnection(server))
    {
        var networkCredential = new NetworkCredential(_username, _password, domain);
        ldapConnection.SessionOptions.SecureSocketLayer = true;
        ldapConnection.AuthType = AuthType.Negotiate;
        ldapConnection.Bind(networkCredential);
    }

    // If the bind succeeds, the credentials are valid
    return true;
}
catch (LdapException ldapException)
{
    // Invalid credentials throw an exception with a specific error code
    if (ldapException.ErrorCode.Equals(ldapErrorInvalidCredentials))
    {
        return false;
    }

    throw;
}

Sources:


On Novell

DirectoryEntry and DirectorySearcher are both high level class tools that are wrappers for Active Directory.

//use the users credentials for the query
DirectoryEntry root = new DirectoryEntry(
    "LDAP://dc=domain,dc=com", 
    loginUser, 
    loginPassword
    );

//query for the username provided
DirectorySearcher searcher = new DirectorySearcher(
    root, 
    "(sAMAccountName=" + loginUser + ")"
    );    

//a success means the password was right
bool success = false; 
try {
    searcher.FindOne();
    success = true;
}
catch {
    success = false;
}

Referred to the answer.

Elutriate answered 18/2, 2018 at 12:53 Comment(5)
This was my very first attempt to validate user/password and because it doesn't works I started to use CompareRequest. What I have found recently is if I turn on Universal Password on an user, It starts validating through "bind" as well as CompareRequest binding with an "admin" user. So my conclusion is that validation user/password pair is not possible without having that feature turned on. I will keep trying other things anyway...Acoustic
@ClownCoder, yes, this question on Authentication Types says that AuthType.Negotiate is Windows specific, so it would not be accepted on Novell.Auxiliaries
You provided a lot of help on this Asif. Thanks!Auxiliaries
Darn. DirectoryEntry and DirectorySearcher are both high level class tools that are wrappers for Active Directory. They are not in the System.DirectoryServices.Protocols namespace. I can't use this answer.Auxiliaries
The part starting with "On Novell" cannot be used on Novell because it has no mechanism to respond to Active Directory calls. That said, back to your first code segment, I found a post HERE for an Apache server that says they got it to work using connection.AuthType = AuthType.Basic; when used in conjunction with a username and password. I have not tested this on our client's Novell system, yet, but it looks logically sound.Auxiliaries
A
4

Let me show you my very best attempt to achieve this validation, maybe it will works for you.

In my context this doesn't work because my admin user can't read attribute "userPassword" and I can't figure why. I guess is some permission not assigned.

Anyway this is the code, hope it helps:

        var server = "<SERVER:PORT>";
        var adminUser = "<USERNAME>";
        var adminPass = "<PASSWORD>";

        using (var ldap = new LdapConnection(server))
        {
            ldap.SessionOptions.ProtocolVersion = 3;
            // To simplify this example I'm not validating certificate. Your code is fine.
            ldap.SessionOptions.VerifyServerCertificate += (connection, certificate) => true;
            ldap.SessionOptions.SecureSocketLayer = true;

            ldap.AuthType = AuthType.Basic;
            ldap.Bind(new System.Net.NetworkCredential($"cn={adminUser},o=<ORGANIZATION>", adminPass));

            // Now I will search to find user's DN.
            // If you know exact DN, then you don't need to search, go to compare request directly.
            var search = new SearchRequest
            {
                //Here goes base DN node to start searching. Node closest to entry improves performance.
                // Best base DN is one level above.
                DistinguishedName = "<BASEDN>", //i.e.: ou=users,o=google
                Filter = "uid=<USERNAME>",
                Scope = SearchScope.OneLevel
            };

            // Adding null to attributes collection, makes attributes list empty in the response.
            // This improves performance because we don't need any info of the entry.
            search.Attributes.Add(null);

            var results = (SearchResponse)ldap.SendRequest(search);

            if (results.Entries.Count == 0)
                throw new Exception("User not found");

            // Because I'm searching "uid" can't exists more than one entry.
            var entry = results.Entries[0];

            // Here I use DN from entry found.
            var compare = new CompareRequest(entry.DistinguishedName, new DirectoryAttribute("userPassword", "<PASSWORD>"));
            var response = (CompareResponse)ldap.SendRequest(compare);

            if (response.ResultCode != ResultCode.CompareTrue)
                throw new Exception("User and/or Password incorrect.");
        }
Acoustic answered 17/2, 2018 at 16:51 Comment(3)
There was another answer posted today by AsifAli72090. It sounds logical: Attempt to Bind using the provided USERNAME and PASSWORD combination. I will not have access to the Novell system for a while. Can you try this code, and let me know if it works on your end?Auxiliaries
@jp2code The code example posted by AsifAli72090 is the simplest and most perfomant way to go. But you need to give bind permission on evey user. If you don't have any restriction (as I do) then that one is the best solution. If you are in a context where you can't allow everybody to bind, then the only way to validate user/password is comparing password through "userPassword" attribute. My first attempt to resolve was this one and it doesn't work in my environment. Will comment on the other answer about a clue I have...Acoustic
When trying CompareRequest, some accounts it kind of works, and some accounts I get an error The requested attribute does not exist. 00002080: AtrErr: DSID-03080155, #1: 0: 00002080: DSID-03080155, problem 1001 (NO_ATTRIBUTE_OR_VAL), data 0, Att 23 (userPassword). For the accounts that "kind of work" they return true even though I changed the password to something else.Collaborative
E
2

On Windows

You can append ContextOptions.Negotiate parameter for ValidateCredentials (Username and Password).

const int ldapErrorInvalidCredentials = 0x31;

const string server = "sd.example.com:636";
const string domain = "sd.example.com";

try
{
    using (var ldapConnection = new LdapConnection(server))
    {
        var networkCredential = new NetworkCredential(_username, _password, domain);
        ldapConnection.SessionOptions.SecureSocketLayer = true;
        ldapConnection.AuthType = AuthType.Negotiate;
        ldapConnection.Bind(networkCredential);
    }

    // If the bind succeeds, the credentials are valid
    return true;
}
catch (LdapException ldapException)
{
    // Invalid credentials throw an exception with a specific error code
    if (ldapException.ErrorCode.Equals(ldapErrorInvalidCredentials))
    {
        return false;
    }

    throw;
}

Sources:


On Novell

DirectoryEntry and DirectorySearcher are both high level class tools that are wrappers for Active Directory.

//use the users credentials for the query
DirectoryEntry root = new DirectoryEntry(
    "LDAP://dc=domain,dc=com", 
    loginUser, 
    loginPassword
    );

//query for the username provided
DirectorySearcher searcher = new DirectorySearcher(
    root, 
    "(sAMAccountName=" + loginUser + ")"
    );    

//a success means the password was right
bool success = false; 
try {
    searcher.FindOne();
    success = true;
}
catch {
    success = false;
}

Referred to the answer.

Elutriate answered 18/2, 2018 at 12:53 Comment(5)
This was my very first attempt to validate user/password and because it doesn't works I started to use CompareRequest. What I have found recently is if I turn on Universal Password on an user, It starts validating through "bind" as well as CompareRequest binding with an "admin" user. So my conclusion is that validation user/password pair is not possible without having that feature turned on. I will keep trying other things anyway...Acoustic
@ClownCoder, yes, this question on Authentication Types says that AuthType.Negotiate is Windows specific, so it would not be accepted on Novell.Auxiliaries
You provided a lot of help on this Asif. Thanks!Auxiliaries
Darn. DirectoryEntry and DirectorySearcher are both high level class tools that are wrappers for Active Directory. They are not in the System.DirectoryServices.Protocols namespace. I can't use this answer.Auxiliaries
The part starting with "On Novell" cannot be used on Novell because it has no mechanism to respond to Active Directory calls. That said, back to your first code segment, I found a post HERE for an Apache server that says they got it to work using connection.AuthType = AuthType.Basic; when used in conjunction with a username and password. I have not tested this on our client's Novell system, yet, but it looks logically sound.Auxiliaries

© 2022 - 2024 — McMap. All rights reserved.