0

I'm working on Web Services which authenticates users against Active Directory. My current solution is working, however, I'm trying to take a different approach.

I have an Active Directory (production) which is sitting behind the firewall. I also installed an Active Directory in DMZ. There's a one way relationship between them. DMZ trusts production and production doesn't care about DMZ.

What I'm trying to accomplish is Authenticate everyone through DMZ Active Directory. Currently, based on the username I know which AD server to authenticate against.

For example, my production Active Directory (let say domain domain.local) and my DMZ Active directory (let say domain domain.public). Before I do authentication against any AD server, I check if the username provided exists in one of the server. Then, I check if the user is active and only then I do authentication. (I'm having issues in the first function. It never reaches to the second or third functions).

UPDATE: ADDED EVERYTHING:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.DirectoryServices;
using System.Security.Principal;
using System.DirectoryServices.AccountManagement;

namespace ActiveDirectory
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in both code and config file together.
public class Service1 : IService1
{
    #region Does User Exist in AD

    public string local = string.Empty;
    public string ldappath = string.Empty;
    public string userNameToUse = string.Empty;
    public string domain = string.Empty;

    public bool DoesUserExist(string userName)
    {
        string _userName = userName;

        bool exist = true;

        using (var domainContext = new PrincipalContext(ContextType.Domain, domain))
        {
            using (var foundUser = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userNameToUse))
            {
                if (foundUser == null)
                {
                    exist = false;
                }
                else
                {
                    return exist;
                }
            }
        }
        return exist;
    }

    #endregion

    #region Check if User Active
    public bool isActive (string userName)
    {
        string _userNameToBeSearched = userNameToUse;
        string _username = string.Empty;
        string _pwd = string.Empty;

        if (local == "YES")
        {
             _username = "xx";
             _pwd = "xx";
            ldappath = "LDAP://xxx/DC=xx, DC=local";
        }
        else
        {
             _username = "xx";
             _pwd = "xx";
             ldappath = "LDAP://xxx/DC=xx, DC=public";
        }

        bool isActive = true;

        try
        {
            DirectoryEntry entry = new DirectoryEntry(ldappath, _username, _pwd);
            DirectorySearcher search = new DirectorySearcher(entry);
            entry.AuthenticationType = AuthenticationTypes.Secure;
            search.SearchRoot = entry;
            search.Filter = "(SAMAccountName=" + _userNameToBeSearched + ")";

            SearchResult results = search.FindOne();

            if (results.ToString() != "")
            {
                int flags = Convert.ToInt32(results.Properties["userAccountControl"][0].ToString());

                //CHECK IF THE ACCOUNT IS DISABLED
                if (flags == 66050)
                {
                    isActive = false;
                }
            }
        }
        catch (DirectoryServicesCOMException ex)
        {
            ex.ToString();
        }
        return isActive;
    }

    #endregion

    #region Is user authenticated
    public string isAuthenticated (string userName, string pwd)
    {
        string _userName, _pwd, message;
        _userName = userName;
        _pwd = pwd;

        char[] splitchar = { '@' };

        string[] strSplit = _userName.Split(splitchar);

        string z = strSplit[0];

        if (strSplit.Length == 2)
        {
            domain = "x.public";
            userNameToUse = z.ToString();
            local = "NO";
        }
        else
        {
            domain = "x.local";
            userNameToUse = z.ToString();
            local = "YES";
        }

        if (DoesUserExist (userNameToUse) == true)
        {
            if (isActive(userNameToUse) == true)
            {
                try
                {
                    DirectoryEntry entry = new DirectoryEntry(ldappath, userNameToUse, _pwd);
                    object nativeObject = entry.NativeObject;
                    var GUIDID = "";

                    using (var domainContext = new PrincipalContext(ContextType.Domain, domain))
                    {
                        using (var user = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userNameToUse))
                        {
                            if (user != null)
                            {
                                GUIDID = user.Guid.ToString();
                            }
                        }
                        message = "Successfully authenticated:" + GUIDID;
                    }
                }
                catch (DirectoryServicesCOMException)
                {
                    message = "Invalid password.";
                }
            }
            else
            {
                message = "Account is disabled";
            }
        }
        else
        {
            message = "There's an issue with your account.";
        }
        return message;      
    }

    #endregion
}
}

If username exists in DMZ AD it will return true else it will return false. However, there're going to be users who will only exists in production AD, but will not have any entry in DMZ. Since, I built ONE WAY trust I should be able to do this:

username@domain.local for production and username@domain.public However, even though I specify fully username if entry doesn't exists in DMZ AD it will return null although it exists in production AD.

Any suggestion, on how I can authenticate everybody through DMZ AD using webservices account which has full permission to production AD?

Note if needed I can provide the rest of the code...*

Thanks

smr5
  • 2,593
  • 6
  • 39
  • 66
  • Which user is used to run the above program? You must use a credential from production AD in order to access both AD. What is the format of `userName` and content of `domain`? – baldpate Dec 23 '14 at 08:23
  • It would be every user. I do have services account in both AD's. Typical usernames will be `sjohns` which will be validated against production and `sjohns@mail.com` which will be validated against DMZ. – smr5 Dec 23 '14 at 15:52
  • If answer from @codingChris does not fit your need, please provide rest of the code. The above method only take 1 parameter `userName`. I still don't quite get where does value for `domain` and `userNameToUse` come from... – baldpate Jan 01 '15 at 18:24
  • @baldpate, thank you for your reply. I added all the code. The function `isAuthenticated` getting called from the application. Let me know if you have any questions. – smr5 Jan 19 '15 at 18:25

2 Answers2

0

What is the value of domain in the PrincipalContext? If you aren't already, you'll need to use the proper domain for the user you're looking up to user your current code. PrincipalContext does not support searches across domains in your forest.

Here is an example of using a DirectorySearcher to do it.

Community
  • 1
  • 1
codingChris
  • 697
  • 4
  • 8
  • thank you for your reply. I added the whole code. This function `isAuthenticated` is called first. – smr5 Jan 19 '15 at 18:25
  • Didn't edit quick enough... 1. Do you have tests? If you can step through this code you could probably pinpoint exactly where the issue is and what's happening. 2. I think after we get passed the verification of the usernames you'll need to use the DirectorySearcher in the DoesUserExist method. I don't think the PrincipalContext is going to work to get to the local domain. Unfortunately I don't have a setup like yours to test. – codingChris Jan 20 '15 at 04:44
  • yes I do have test results. The very first thing that is happening is `DoesUserExist`. Here I check if this username exists at all. I'm checking the DMZ domain, however, I'm passing the usernames `jsmith@domain.local`. The following line `var foundUser = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, userNameToUse)` returns null. That user doesn't exist in DMZ but it does exist in production. So by qualifying the domain shouldn't it return the user? – smr5 Jan 20 '15 at 16:14
  • Even though you know which domain you should be searching in, PrincipalContext does not support searching for users in the .local domain from the .public dmz server. Instead of using S.DS.AM and FindByIdentity, you're going to need to setup a DirectorySearcher in that method instead. – codingChris Jan 20 '15 at 21:45
  • I'm leaning toward `DirectorySearcher1`, however, as I'm writing some code I found out that I can't search in multiple domains. I have two domains and users are located in both of them, yet there will be cases where users only exist in one domain but not in other. Any suggestion on how to search in multiple domains? Thanks... – smr5 Jan 20 '15 at 21:49
0

The code has several issues, I only talk about major ones.

  1. In method DoesUserExist you use service account (no credential is passed to PrincipalContext) to query both domains. But in method isActive you have different credentials for different domains. So we have 3 credentials for 2 domains...

    At least use a consistent way for both methods.

  2. For 1-way trust(e.g. A trusts B), accounts in B should be able to access both domain A and B.

    May be you can simply use an account in trusted domain as service account. And then use service account (null to username and password) to perform all AD access. So you don't even need to put password in your code.

  3. In isActive, where does the "66050" come from?
    66060 is 10202 in hex, which means (1)user, (2)disabled and (3)don't expire password.

    To check if an account is enabled, check only the ACCOUNTDISABLE bit (0x0002). It should be 0 for enabled.

  4. You already get the UserPrincipal in DoesUserExist. Can simply check enabled by looking at UserPrincipal.Enabled.

I need to do code review in my daily work. Now I'm doing it also on Stack Overflow... :)

baldpate
  • 1,707
  • 1
  • 14
  • 23
  • thank you for your reply. I just started writing this code and it'll most likely keep changing. My main goal here is to do `Authentication` for provided `username` and `password`. For example, `A (dmz)` trusts `B (production)` and user `jsmith` exists only in `B`, but authentication needs to be done using `A`. What approach should I take? Thanks. – smr5 Jan 20 '15 at 15:39