30

In testing our .NET 4.0 application under .NET 4.5, we've encountered a problem with the FindByIdentity method for UserPrincipal. The following code works when run in a .NET 4.0 runtime, but fails under .NET 4.5:

[Test]
public void TestIsAccountLockedOut()
{
    const string activeDirectoryServer = "MyActiveDirectoryServer";
    const string activeDirectoryLogin = "MyADAccount@MyDomain";
    const string activeDirectoryPassword = "MyADAccountPassword";
    const string userAccountToTest = "TestUser@MyDomain";
    const string userPasswordToTest = "WRONGPASSWORD";

    var principalContext = new PrincipalContext(ContextType.Domain, activeDirectoryServer, activeDirectoryLogin, activeDirectoryPassword);

    var isAccountLockedOut = false;
    var isAuthenticated = principalContext.ValidateCredentials(userAccountToTest, userPasswordToTest, principalContext.Options);
    if (!isAuthenticated)
    {
        // System.DirectoryServices.AccountManagement.PrincipalOperationException : Information about the domain could not be retrieved (1355).
        using (var user = UserPrincipal.FindByIdentity(principalContext, IdentityType.UserPrincipalName, userAccountToTest))
        {
            isAccountLockedOut = (user != null) && user.IsAccountLockedOut();
        }
    }
    Assert.False(isAuthenticated);
    Assert.False(isAccountLockedOut);
}

Here is the exception stack trace:

System.DirectoryServices.AccountManagement.PrincipalOperationException : Information about the domain could not be retrieved (1355).
at System.DirectoryServices.AccountManagement.Utils.GetDcName(String computerName, String domainName, String siteName, Int32 flags)   at System.DirectoryServices.AccountManagement.ADStoreCtx.LoadDomainInfo()   at 
System.DirectoryServices.AccountManagement.ADStoreCtx.get_DnsDomainName()   at System.DirectoryServices.AccountManagement.ADStoreCtx.GetAsPrincipal(Object storeObject, Object discriminant)   at 
System.DirectoryServices.AccountManagement.ADStoreCtx.FindPrincipalByIdentRefHelper(Type principalType, String urnScheme, String urnValue, DateTime referenceDate, Boolean useSidHistory)   at 
System.DirectoryServices.AccountManagement.ADStoreCtx.FindPrincipalByIdentRef(Type principalType, String urnScheme, String urnValue, DateTime referenceDate)   at 
System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithTypeHelper(PrincipalContext context, Type principalType, Nullable`1 identityType, String identityValue, DateTime refDate)   at 
System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithType(PrincipalContext context, Type principalType, IdentityType identityType, String identityValue)   at 
System.DirectoryServices.AccountManagement.UserPrincipal.FindByIdentity(PrincipalContext context, IdentityType identityType, String identityValue)   

Has anyone else seen and resolved this problem? If not, is there a better way for us to check the IsAccountLockedOut status for an Active Directory account?

For reference, all of our test machines are within the same subnet. There are separate ActiveDirectory servers running Windows Server 2003, 2008 and 2012, in a variety of domain functional modes (see below). The code works from machines running .NET 4.0, but fails from machines running .NET 4.5.

The three .NET machines we ran the code from are:
- Windows 7 running .NET 4.0
- Windows Vista running .NET 4.5
- Windows Server 2012 running .NET 4.5

The Active Directory servers we've tried are:
- Windows 2003 with AD Domain Functional Mode set to Windows 2000 native
- Windows 2003 with AD Domain Functional Mode set to Windows Server 2003
- Windows 2008 with AD Domain Functional Mode set to Windows 2000 native
- Windows 2008 with AD Domain Functional Mode set to Windows Server 2003
- Windows 2008 with AD Domain Functional Mode set to Windows Server 2008
- Windows 2012 with AD Domain Functional Mode set to Windows 2012

All of those Active Directory servers are configured as a simple, single forest, and the client machines are not part of the domain. They are not used for any other function than to test this behavior, and aren't running anything other than Active Directory.


EDIT - 9 Oct 2012

Thanks to everyone that replied. Below is a C# command-line client that demonstrates the problem, and a short-term workaround that we identified that didn't require us to change anything about the Active Directory and DNS configurations. It appears that the exception is only thrown once with an instance of the PrincipalContext. We included the outputs for a .NET 4.0 machine (Windows 7) and a .NET 4.5 machine (Windows Vista).

using System;
using System.DirectoryServices.AccountManagement;

namespace ADBug
{
    class Program
    {
        static void Main(string[] args)
        {
            const string activeDirectoryServer = "MyActiveDirectoryServer";
            const string activeDirectoryLogin = "MyADAccount";
            const string activeDirectoryPassword = "MyADAccountPassword";
            const string validUserAccount = "TestUser@MyDomain.com";
            const string unknownUserAccount = "UnknownUser@MyDomain.com";

            var principalContext = new PrincipalContext(ContextType.Domain, activeDirectoryServer, activeDirectoryLogin, activeDirectoryPassword);

            // .NET 4.0 - First attempt with a valid account finds the user
            // .NET 4.5 - First attempt with a valid account fails with a PrincipalOperationException
            TestFindByIdentity(principalContext, validUserAccount, "Valid Account - First Attempt");
            // Second attempt with a valid account finds the user
            TestFindByIdentity(principalContext, validUserAccount, "Valid Account - Second Attempt");
            // First attempt with an unknown account does not find the user
            TestFindByIdentity(principalContext, unknownUserAccount, "Unknown Account - First Attempt");
            // Second attempt with an unknown account does not find the user (testing false positive)
            TestFindByIdentity(principalContext, unknownUserAccount, "Unknown Account - Second Attempt");
            // Subsequent attempt with a valid account still finds the user
            TestFindByIdentity(principalContext, validUserAccount, "Valid Account - Third Attempt");
        }

        private static void TestFindByIdentity(PrincipalContext principalContext, string userAccountToTest, string message)
        {
            var exceptionThrown = false;
            var userFound = false;
            try
            {
                using (var user = UserPrincipal.FindByIdentity(principalContext, IdentityType.UserPrincipalName, userAccountToTest))
                {
                    userFound = (user != null);
                }
            }
            catch (PrincipalOperationException)
            {
                exceptionThrown = true;
            }
            Console.Out.WriteLine(message + " - Exception Thrown  = {0}", exceptionThrown);
            Console.Out.WriteLine(message + " - User Found = {1}", userAccountToTest, userFound);
        }
    }
}

.NET 4.0 Output

Valid Account - First Attempt - Exception Thrown  = False
Valid Account - First Attempt - User Found = True
Valid Account - Second Attempt - Exception Thrown  = False
Valid Account - Second Attempt - User Found = True
Unknown Account - First Attempt - Exception Thrown  = False
Unknown Account - First Attempt - User Found = False
Unknown Account - Second Attempt - Exception Thrown  = False
Unknown Account - Second Attempt - User Found = False
Valid Account - Third Attempt - Exception Thrown  = False
Valid Account - Third Attempt - User Found = True

.NET 4.5 Output

Valid Account - First Attempt - Exception Thrown  = True
Valid Account - First Attempt - User Found = False
Valid Account - Second Attempt - Exception Thrown  = False
Valid Account - Second Attempt - User Found = True
Unknown Account - First Attempt - Exception Thrown  = False
Unknown Account - First Attempt - User Found = False
Unknown Account - Second Attempt - Exception Thrown  = False
Unknown Account - Second Attempt - User Found = False
Valid Account - Third Attempt - Exception Thrown  = False
Valid Account - Third Attempt - User Found = True
  • 1
    Not sure if you've googled this yet, but I found a post with several comments for fixes for various scenarios that might cause this: http://elegantcode.com/2009/03/21/one-scenario-where-the-systemdirectoryservices-accountmanagement-api-falls-down/ I've never seen it myself, but in the interest of being helpful, I thought I'd share this as it LOOKS useful to me. – David Sep 26 '12 at 19:31
  • for example have you Tried passing in DC and CN for example PrincipalContext ctx = new PrincipalContext( ContextType.Domain, "fabrikam.com", "CN=Users,DC=fabrikam,DC=com", "administrator", "securelyStoredPassword"); – MethodMan Sep 26 '12 at 19:51
  • DJ, We just tried passing in the CN & DC string, and it still works under .NET 4.0 and fails under .NET 4.5. – Grand Avenue Software Sep 26 '12 at 20:34
  • David, I don't think there is a setup issue since it does work under .NET 4.0. We added additional background information about our test environments to the original description. Thanks. – Grand Avenue Software Sep 26 '12 at 20:37
  • 4
    Just wanted to confirm that you're not the only ones having this problem. I've got a nearly identical piece of code that's throwing the same exception at FindByIdentity. I've run it on three computers with identical network settings, and only the one with .NET 4.5 is failing. I'm not quick to declare bugs as external, but it did work before the upgrade. – InsqThew Sep 27 '12 at 20:33
  • I have forwarded this issue to the product team in Microsoft. Somebody will get back to you early next week. – Anand Sep 30 '12 at 06:52
  • Can you please provide OS information for domain controller, Machine where its failing? Product team could not repro the issue with the information in the post. Also little bit domain information and the server string you are passing in the PrincipalContext constructor – Anand Oct 02 '12 at 22:34
  • @Anand - The original post has been updated with detailed information about the tested client and server configurations. Here are the strings that we use internally for our tests: `const string activeDirectoryServer = "vad2008"; const string activeDirectoryLogin = "grandavenue@adtest2008.grandavenue.com"; const string activeDirectoryPassword = "ADtest123"; const string userAccountToTest = "SimpleUser@adtest2008.grandavenue.com"; const string userPasswordToTest = "WRONGPASSWORD";` – Grand Avenue Software Oct 03 '12 at 16:50
  • Is there any kind of publicly accessible ActiveDirectory server that we could run tests against, to eliminate the possibility that it's a configuration problem on our end? – Grand Avenue Software Oct 04 '12 at 17:57
  • For what it's worth, your code ran fine on my machine compiled under .NET 4.5. Is grandavenue@adtest2008.grandavenue.com a domain admin? – CodeGrue Oct 05 '12 at 19:36
  • @CodeGrue Thanks. The account we're using to construct the PrincipalContext (grandavenue@adtest2008.grandavenue.com) is just a normal user with no special permissions. Is your client machine a member of the domain? Ours are not, so aren't relying on any additional authorization that would come from that. Could that be why yours is working? – Grand Avenue Software Oct 05 '12 at 20:49
  • 1
    I also confirm this issue. usingd vmware snapshots to bounce a system pre/post .net 4.5 install to verify that was when the issue starts. We are also querying from machines that are not on the domain that is being queried. Simply installing .net 4.5 causes working code to start failing targeting either .net 4 or 4.5. We are using vs2010 targeting .net 4.0. changing the target to 3.5 and the code works even post 4.5 install. – bkr Oct 06 '12 at 17:08
  • and just to add - our DCs are win2k8r2 at functional level win2k8 r2, our calling clients are win2k8r2 machines also. – bkr Oct 06 '12 at 17:14
  • @Anand One more piece of information. Using a Windows 2012 Active Directory domain controller at functional level win2012 and a Windows 7 client the following was done. .NET 4.0 on client -> test passes. Upgraded to .NET 4.5 -> test fails. Changed client to have the AD server as it's DNS server -> test passes. This is true even if we use an IP address instead of a server name to connect in the principal context. – Grand Avenue Software Oct 08 '12 at 19:21
  • @Anand Another test case: We created a simple command-line test client, and when we ran it on the Active Directory 2012 server itself (under .NET 4.5) it worked fine. So we're continuing to suspect this has something to do with the configuration of the domain servers and the clients. – Grand Avenue Software Oct 08 '12 at 20:26
  • @GrandAvenueSoftware Thanks for these details. This will help reproing the issue locally. – Anand Oct 09 '12 at 06:16
  • I'm from .NET Framework - CLR team. We're unable to reproduce the issue in our internal machine. It is probably related to something that's not present in our internal environment. If anyone has a repro handy, would you be willing to allow us to debug on your machine? Please email us on netfx45compat at Microsoft dot com to coordinate. After taking a look at the repro, we will post back results on this post. Thanks in advance! --Varun, Microsoft .NET Framework Compatibility. – Varun Oct 15 '12 at 00:38

3 Answers3

12

We are experiencing the exact same issue (cross domain queries failing on updating to 4.5) - I would consider this a bug since it breaks existing (4.0) code.

However, in the interest of making it work - taking a look at one of the (now) failing clients, I noticed that there were a bunch of DNS requests for SRV records that were failing, of the form:

_ldap._tcp.MYSERVER1.mydomain.com,INet,Srv
_ldap._tcp.dc._msdcs.mydomain.com,INet,Srv

Modifying our DNS server (the DNS used by the failing clients) to have a forward zone for all mydomain.com traffic to one of the DCs on the domain did resolve the issue.

Using nslookup, the behavior from before (when it was failing) to now (working) was that before those queries would return "Non-existent domain" whereas now they return "* No Service location (SRV) records available for ...". The point of failure seems to be the perceived nonexistence of the domain rather than missing SRV records. Hopefully MS reverts this behavior but in the meantime you might have some luck creating a DNS forward zone if you can control the DNS for the failing clients.

bkr
  • 1,444
  • 1
  • 11
  • 22
6

To the OP (and anyone else that helped with replies) we have(had) the same exact issue. In our development environment, installed VS2012 and our app broke at runtime during login (AD issue as pointed out above). So I had my system wiped and continued using 2010, all the while shedding a tear every time Id read a new blog post about how awesome 2012 is blah blah.

So I found this thread thanks to Scott Hanselman. I installed a VM on my development box, Windows 8 developer 90day preview on it, and VS2012. Got our Application up and running and immediately was hit with the login AD snag. Simply wrapped our FindByIdentity in a try catch and forced it to try again after the first catch - and viola it works!! So thanks to whoever figured that little trick out!!

So, its a minor fix, and a "hack" that works for local development, and shouldn't affect production since we aren't putting 4.5 on production any time soon.

But the downside is that locally, logging in now takes like 2 minutes versus seconds when we ran under 2010 :(

I don't really know what else I can provide to actually try to help solve the situation, but figured Id share my 2 cents anyway since this still appears to be a major issue.

jkat98
  • 260
  • 1
  • 3
  • 8
  • 3
    Thanks for the feedback. We confirmed the same behavior (first attempt fails, all subsequent succeed) and posted a test client above. The last feedback we received from Microsoft was the following: _"SDS.AM in .Net4.5 added a restriction to require domain DNS resolution on the client side. This was designed as DNS best practice should have correct DNS entry. Based on the feedback from the forum, we are considering to remove the restriction in the future release."_ – Grand Avenue Software Nov 10 '12 at 00:30
  • Thanks! One thing I also noticed is that after adding the first attempt failure catch - theres also a sizeable delay in any calls to AD - talking 3 minutes+. Are there entries to my local hosts I can add to fix this kind of issue? Thanks again for the feedback! – jkat98 Nov 12 '12 at 16:11
  • I'm not sure if this is related, but we've always had significant performance issues with using hostnames (e.g. myserver) instead of IP addresses (e.g. 192.168.14.3) for the server. We haven't done any performance testing of the try-catch workaround for this problem, but I'll throw together a test case and see what kind of numbers we come up with. – Grand Avenue Software Nov 12 '12 at 20:06
  • Nice one!! try-catch'ing the FindByIdentity() call twice worked out! -Thanks – Lin-Art Feb 04 '13 at 21:16
1

Had same problem after upgrading .net framework from 4.0 to 4.5 I have ugpraded framework to .net 4.5.1 and it worked.

nefarel
  • 596
  • 4
  • 10
  • I am having a similar issue with `IsMemberOf()` method. My .net version is 4.5.2 but DNS seems to be hard requirement, which my test machines could not fulfill. Any workaround ? – mittal Sep 09 '18 at 10:02
  • I started a new thread here https://stackoverflow.com/questions/52242019/net-4-5-2-ismemberof-requires-dns-configuration – mittal Sep 09 '18 at 10:03