tags:

views:

1317

answers:

4

I am hosting a WCF Web Service with IIS 6.0. My application pool is running under a local Administrator account, and I have other local users defined for accessing the Web Service. I've written the following code to validate users:


//Any public static (Shared in Visual Basic) members of this type are thread safe
public static PrincipalContext vpc;

//static initializer
static UserManagement()
{
    vpc = new PrincipalContext(ContextType.Machine);
}

//Determines whether the given credentials are for a valid Administrator
public static bool validateAdminCredentials(string username, string password)
{
    using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
    {
        if (vpc.ValidateCredentials(username, password))
        {
            using (UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, username))
            {
                foreach (GroupPrincipal gp in user.GetGroups())
                {
                    try
                    {
                        if (gp.Name.Equals("Administrators"))
                        {
                            return true;
                        }
                    }
                    finally
                    {
                        gp.Dispose();
                    }
                }
            }
        }

        return false;
    } //end using PrincipalContext
}

...and in another class:


//verify the user's password
if (!UserManagement.vpc.ValidateCredentials(username, password))
{
    string errorMsg = string.Format("Invalid credentials received for user '{0}'", username);
    throw new Exception(errorMsg);
}

(Note: I am using the public static PrincipalContext (vpc) solely for calling the ValidateCredentials method; I create a different, temporary PrincipalContext for creating, deleting, and finding users and groups, as I got various COM-related errors when I tried using the global PrincipalContext for everything).

So, most of the time, this code works wonderfully. However, intermittently, I get the following error:

Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. (Exception from HRESULT: 0x800704C3)

Application: System.DirectoryServices.AccountManagement

Stack Trace: at System.DirectoryServices.AccountManagement.CredentialValidator.BindSam(String target, String userName, String password) at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password) at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password) at MyNamespace.User..ctor(String username, String password)

Once the error occurs, I continue to get it until I restart my entire server (not just IIS). I've tried restarting my application pool and/or IIS, but the error does not go away until I restart the machine. I've also tried instantiating (via a using block) a new PrincipalContext for every call to ValidateCredentials (which I shouldn't have to do), but I still eventually get the same error. From what I've read on System.DirectoryServices.AccountManagement (msdn docs, articles), I believe I'm using it correctly, but this error is crippling my application! I need (and should be able) to validate local user credentials from web service requests coming from multiple clients. Am I doing something wrong? Any help on solving this issue would be much appreciated...

A: 

Hello, I am also getting the "Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed....." error when calling ValidateCredentials for a local user account. I can't see what I am doing wrong.

Did you find a solution?

+1  A: 

My suggestion would be to not use ValidateCredentials with the SAM provider. I believe it should work, but I am also not surprised that it runs into problems. I think you'll do better by doing a p/invoke into LogonUser using Network logon type for programmatic credential validation.

I'd like to see if Microsoft has a known issue and solution to this problem but in the meantime I'd suggest just coding around it.

Joe Kaplan
A: 

If you need a solution the works even with Windows 2000 and w/o running code as system you could also use this:

public static bool ValidateUser(
    string userName, 
    string domain, 
    string password)
{
    var tcpListener = new TcpListener(IPAddress.Loopback, 0);
    tcpListener.Start();

    var isLoggedOn = false;
    tcpListener.BeginAcceptTcpClient(
        delegate(IAsyncResult asyncResult)
        {
            using (var serverSide = 
                new NegotiateStream(
                     tcpListener.EndAcceptTcpClient(
                        asyncResult).GetStream()))
            {
                try
                {
                serverSide.AuthenticateAsServer(
                    CredentialCache.DefaultNetworkCredentials,
                    ProtectionLevel.None, 
                    TokenImpersonationLevel.Impersonation);
                var id = (WindowsIdentity)serverSide.RemoteIdentity;
                isLoggedOn = id != null;
                }
                catch (InvalidCredentialException) { }
            }
        }, null);

    var ipEndpoint = (IPEndPoint) tcpListener.LocalEndpoint;
    using (var clientSide = 
        new NegotiateStream(
            new TcpClient(
                ipEndpoint.Address.ToString(),
                ipEndpoint.Port).GetStream()))
    {
        try
        {
            clientSide.AuthenticateAsClient(
                new NetworkCredential(
                    userName,
                    password,
                    domain),
                "",
                ProtectionLevel.None,
                TokenImpersonationLevel.Impersonation);
        }
        catch(InvalidCredentialException){}
    }
    tcpListener.Stop();
    return isLoggedOn;
}
Daniel Fisher lennybacon