Strange issue with System.DirectoryServices.AccountManagement.UserPrincipal.FindByIdentity - memory-leaks

We're writing a system that allows a user to change their account password through a web application on our intranet.
At first, everything appeared to be running smoothly. During development passwords for our test accounts could be changed with no problem.
When we made the system live, however, we started running into issues. Here are the symptoms:
At first, everything is fine. Users
can change their passwords.
At some
point, the following error occurs in
UserPrincipal.FindByIdentity:
"System.Runtime.InteropServices.COMException:
The authentication mechanism is
unknown. "
From then on, trying to
change a password through the web
application results in the error:
"System.Runtime.InteropServices.COMException:
The server is not operational. "
If I manually recycle the app pool,
everything seems to fix itself until
more errors begin happening... i.e.,
the process starts all over again at
phase 1.
Here's the relevant snippet of code:
private static PrincipalContext CreateManagementContext() {
return new PrincipalContext(
ContextType.Domain,
ActiveDirectoryDomain,
ActiveDirectoryManagementAccountName,
ActiveDirectoryManagementAccountPassword);
}
private static void ChangeActiveDirectoryPasword(string username, string password) {
if (username == null) throw new ArgumentNullException("username");
if (password == null) throw new ArgumentNullException("password");
using (var context = CreateManagementContext())
using (var user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username)) {
user.SetPassword(password);
}
}
Any clues as to why this is happening? Google searches aren't turning up anything really helpful, and neither are the docs on MSDN.

First thing I notice is that you are using UserPrincipal.FindByIdentity which is inherited from AuthenticablePrincipal which is inherited from Principal. I say all this because the Principal class has a known memory leak in the FindByIdentity. If you have a look at this MSDN entry, you will notice at the bottom that Gary Caldwell from Microsoft said the following:
This call has an unmanaged memory leak
because the underlying implemenation
uses DirectorySearcher and
SearchResultsCollection but does not
call dispose on the
SearchResultsCollection as the
document describes.
I would guess that this is your issue. The memory leak causes the Application Pool to fill up and finally cause errors, until the Application Pool is reset and the memory is disposed.
When we use any active directory functions, we use the following to accomplish setting of the user's password:
Public Shared Function GetUserAccount(ByVal username As String) As DirectoryEntry
Dim rootPath As String = GetRootPath()
Using objRootEntry As New DirectoryEntry(rootPath)
Using objAdSearcher As New DirectorySearcher(objRootEntry)
objAdSearcher.Filter = "(&(objectClass=user)(samAccountName=" & username & "))"
Dim objResult As SearchResult = objAdSearcher.FindOne()
If objResult IsNot Nothing Then Return objResult.GetDirectoryEntry()
End Using
End Using
Return Nothing
End Function
Public Shared Sub SetPassword(ByVal username As String, ByVal newPassword As String)
Using objUser As DirectoryEntry = GetUserAccount(username)
If objUser Is Nothing Then Throw New UserNotFoundException(username)
Try
objUser.Invoke("SetPassword", newPassword)
objUser.CommitChanges()
Catch ex As Exception
Throw New Exception("Could not change password for " & username & ".", ex)
End Try
End Using
End Sub
Also, if you're wanting the users to change the passwords directly and you don't want to rely on their honesty, you might want to consider using the ChangePassword function of LDAP like this:
Public Shared Sub ChangePassword(ByVal username As String, ByVal oldPassword As String, ByVal newPassword As String)
Using objUser As DirectoryEntry = GetUserAccount(username)
If objUser Is Nothing Then Throw New UserNotFoundException(username)
Try
objUser.Invoke("ChangePassword", oldPassword, newPassword)
objUser.CommitChanges()
Catch ex As TargetInvocationException
Throw New Exception("Could not change password for " & username & ".", ex)
End Try
End Using
End Sub
This forces the user to know the prior password before changing to the new one.
I hope this helps,
Thanks!

Related

Credential Provider accepts old Windows Live account passwords

For one of our customers, we created a custom credential provider which receives a decryption key and the filename of an encrypted file which container the username and password. This mechanism works perfectly for local user accounts. The user is authenticated when needed, and the old password is no longer accepted right after the user changes his password.
However, for windows live accounts the user can sometimes login using his old password after changing his password online (accounts.microsoft.com) and even after logging in to windows with the newly created password. Strange thing is, that the user cannot login by typing his old password. It only works when using the credential provider.
To make it more confusing, sometimes it works as expected and the behavior seems to differ from machine to machine.
My gut feeling tells me, there is something wrong with the code we use to authenticate the user, but I cannot figure out what is going wrong. We already tried to set the OldPasswordAllowedPeriod registry value, but this seems not to work.
We use the following GetSerialization() implementation, to fill the authentication buffer:
public int GetSerialization(...)
{
pcpgsr = _CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE.CPGSR_NO_CREDENTIAL_NOT_FINISHED;
pcpcs = new _CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION();
ppszOptionalStatusText = string.Empty;
pcpsiOptionalStatusIcon = _CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_NONE;
try
{
var inCredSize = 0;
var inCredBuffer = Marshal.AllocCoTaskMem(0);
if (string.IsNullOrEmpty(_username) || _password == null || _password.Length == 0)
{
return SetAuthenticationError(out pcpgsr, out pcpsiOptionalStatusIcon, out ppszOptionalStatusText, "This NFC card has not been registered on this screen.");
}
if (!PInvoke.CredPackAuthenticationBuffer(0, _username, SecureStringToString(_password), inCredBuffer, ref inCredSize))
{
Marshal.FreeCoTaskMem(inCredBuffer);
inCredBuffer = Marshal.AllocCoTaskMem(inCredSize);
if (PInvoke.CredPackAuthenticationBuffer(0, _username, SecureStringToString(_password), inCredBuffer, ref inCredSize))
{
pcpgsr = _CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE.CPGSR_RETURN_CREDENTIAL_FINISHED;
pcpsiOptionalStatusIcon = _CREDENTIAL_PROVIDER_STATUS_ICON.CPSI_SUCCESS;
pcpcs.clsidCredentialProvider = Guid.Parse(Constants.CredentialProviderUID);
pcpcs.rgbSerialization = inCredBuffer;
pcpcs.cbSerialization = (uint)inCredSize;
RetrieveNegotiateAuthPackage(out var authPackage);
pcpcs.ulAuthenticationPackage = authPackage;
return HResult.S_OK;
}
_logger.LogError($"Failed to pack credentials for: {_username}.");
return SetAuthenticationError(out pcpgsr, out pcpsiOptionalStatusIcon, out ppszOptionalStatusText, "Failed to pack credentials.");
}
_logger.LogWarning("GetSerialization unexpectedly preliminary succesfully buffered credentials");
return SetAuthenticationError(out pcpgsr, out pcpsiOptionalStatusIcon, out ppszOptionalStatusText, "Something unexpected went wrong!");
}
catch (Exception ex)
{
// In case of any error, do not bring down winlogon
_logger.LogError(ex);
return SetAuthenticationError(out pcpgsr, out pcpsiOptionalStatusIcon, out ppszOptionalStatusText, "Something unexpected went wrong!");
}
finally
{
_shouldAutoLogin = false; // Block auto-login from going full-retard
}
}
Can someone point me in the right direction to solve this issue? Or, has someone any idea on what we are doing wrong when authenticating the user, using our custom credential provider?
Thanks in advance!

Setting up Azure Key Vault

I have been battling with using Azure Key Vault in both development and production versions of my app for several days now. I can't seem to set things up correctly to gain access to my key vault from my app running locally during debug in VS 2017 or when deployed as a Web App on Azure. When I use the CLI with my account and resource group set I have no problem accessing a secret in my vault. However, when I try to access the same secret using the code below in my app I get an error stating that the access token was not obtained (paraphrased).
Imports System.Threading.Tasks
Imports Microsoft.Azure.KeyVault
Imports Microsoft.Azure.KeyVault.Models
Imports Microsoft.Azure.Services.AppAuthentication
Imports Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider
Public Class SocialXXXXXXX
Inherits System.Web.UI.Page
Public Property Message As String
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Message = "Your application description page."
Dim retries As Integer = 0
Dim retry As Boolean = False
Try
Dim azureServiceTokenProvider As AzureServiceTokenProvider = New AzureServiceTokenProvider()
Dim keyVaultClient As KeyVaultClient = New KeyVaultClient(New KeyVaultClient.AuthenticationCallback(AddressOf GetAccessTokenAsync))
Dim secret = keyVaultClient.GetSecretAsync("https://XXXXXXXvault.vault.azure.net/secrets/ExamplePassword/52ec77ddc2dXXXXXXX6c63f6c9").Result
TextBox7.Text = secret.Value
Catch keyVaultException As KeyVaultErrorException
TextBox7.Text = keyVaultException.Message
End Try
End Sub
Private Shared Function getWaitTime(ByVal retryCount As Integer) As Long
Dim waitTime As Long = (CLng(Math.Pow(2, retryCount)) * 100L)
Return waitTime
End Function
Public Async Function GetAccessTokenAsync() As Task(Of String)
Dim azureServiceTokenProvider = New AzureServiceTokenProvider()
Dim accessToken As String = Await azureServiceTokenProvider.GetAccessTokenAsync("https://XXXXXXXvault.vault.azure.net/")
Return accessToken
End Function
I would like to be able to access my secrets during development and production.
Update:
Another user over at https://forums.asp.net/p/2158070/6271824.aspx?p=True&t=636995498339724660 suggested a similar approach as #Joyey Cai below. However, I am reluctant to go that route as instead of using MSI, it requires me to include my app secret either in my code or in a setting. I have gone through your many tutorials and even deleted everything other than my web app from Azure and started over following MS's tutorial at https://learn.microsoft.com/en-us/learn/modules/manage-secrets-with-azure-key-vault/1-introduction and followed microsoft's recommendations verbatim (twice actually) and still have had not luck. I have also found that others are having the same issue and thus far I have not found that anyone has solved it. The problem appears to reside in the fact that Microsoft.Azure.Services.AppAuthentication is not authenticating the account which the developer is using for Azure Service Authentication (Tools>Azure>Azure Service Authentication in VS). I am also unsure if something else may be wrong with Microsoft.Azure.Services.AppAuthentication since not only will my app not get the secret when debugging locally but it also does not get the secret when deployed on the same machine that the key vault resides on. This is a very perplexing problem.
You could use the code as below and add permission to your application.
Imports Microsoft.Azure.KeyVault
Imports Microsoft.IdentityModel.Clients.ActiveDirectory
Public Class Class1
Shared appId As String = "xxxxxxxxxxxxxxx"
Shared appSecret As String = "xxxxxxxxxxxxxxx"
Shared tenantId As String = "xxxxxxxxxxxxxxx"
Public Shared Sub Main()
Dim kv = New KeyVaultClient(AddressOf GetAccessToken)
Dim scret = kv.GetSecretAsync("https://yourkeyvaultname.vault.azure.net", "yoursecretname").GetAwaiter().GetResult().Value
End Sub
Public Shared Async Function GetAccessToken(ByVal azureTenantId As String, ByVal clientId As String, ByVal redirectUri As String) As Task(Of String)
Dim context = New AuthenticationContext("https://login.windows.net/" & tenantId)
Dim credential = New ClientCredential(appId, appSecret)
Dim tokenResult = Await context.AcquireTokenAsync("https://vault.azure.net", credential)
Return tokenResult.AccessToken
End Function
End Class
Also, you need to add permission with "Key Vault" to the registered app.
In Key vault channel, you need to Add policies to your registered application or user. And in Access Control you need to add permission to your registered application or user.
The result:
Here is the C# code sample you can refer to.

NUnit & MVC4 SimpleMembership

I'm having a really difficult time creating unit tests for my MVC4 application, using NUnit. Right now I'm just focusing on this one test in particular, which logs a user in OR creates them if they don't exist, and then logs them in. Here is the test :
[Test]
public void LoginValidUser()
{
//Start up the DB connection
App_Start_DB();
//Setup our default test user
string UserName = "LocalTestUser#penrouse.com";
string Password = "books";
bool LoginWorked = false;
//check and see if our test user exists
if (!WebSecurity.UserExists(UserName))
{
//If not, create them
WebSecurity.CreateUserAndAccount(UserName, Password, new
{
Name = "Local Test User",
IsPromotional = true,
IsAllowShare = true
});
//Log them in
LoginWorked = WebSecurity.Login(UserName, Password);
}
else
{
//This user already exists, just log them in
LoginWorked = WebSecurity.Login(UserName, Password);
}
Assert.IsTrue(LoginWorked);
Trace.WriteLine("Login Valid User Result : " + LoginWorked.ToString());
}
The problem is that every time I attempt WebSecurity.Login(), I get a null reference exception, and the stack trace points to :
System.Web.Security.FormsAuthentication.SetAuthCookie(String userName, Boolean createPersistentCookie, String strCookiePath);
Calling that method directly before the login attempt the does not change the behavior. Thus, I have two questions :
Is there a better way to go about testing these parts of SimpleMembership?
If not, is there a good way to override or mock out the AuthCookie so that the login will work when tested in this way?
Any help\insight would be hugely appreciated.

How to Deactivate a LDAP User?

I am using a library to authenticate LDAP Users, whose code is as follows:
public void authUser(String username, String pwd)
throws Exception
{
try
{
Properties env = getEnvironmentForContext();
env.put("java.naming.security.principal", "uid=" +
username + ",ou=users, dc=company"));
env.put("java.naming.security.credentials", pwd);
context = getContext(env);
System.out.println("Authentication Succeeded");
}
catch (Exception e)
{
System.out.println("Authentication Failed");
throw e;
}
}
Please note, i cannot modify the above Authentication Code. It comes from a external Library.
But, i want to deactivate some users (not delete them), so that Authentication Fails.
I am using LDAP (not Active Directory). Do not know what LDAP Software it is though, i can connect to it using 'LDAP Browser Client'.
The users exist under: dc=company, ou=users, uid=username
What attribute can i add/change on LDAP 'user' to de-activate a user.
Could i move the user to a different group like: dc=company, ou=deactivatedusers, uid=username? But this is not the preferred option, plus am not sure best way to do that.
EDIT: The LDAP being used is: Netscape/Sun/iPlanet
To answer your question per the Oracle iPlanet (Sun) documentation :
Setting the attribute nsAccountLock to true will disable a users account, and prevent them from binding to the directory.
However, in terms of the code you already have, I just don't see any way of accomplishing this... Is there something preventing you from writing your own implementation for iPlanet using the System.DirectoryServices.Protocols namespace in .Net?
Here is how I bind and authorize users against an iPlanet server :
//Build servername from variables
var BuildServerName = new StringBuilder();
BuildServerName.Append(ServerName);
BuildServerName.Append(":" + Convert.ToString(Port));
var ldapConnection = new LdapConnection(BuildServerName.ToString());
//Authenticate the Admin username and password, making sure it's a valid login
try
{
//Pass in the network (administrative) creds, and the domain.
var networkCredential = new NetworkCredential(Username, Password, config.LdapAuth.LdapDomain);
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
ldapConnection.AuthType = AuthType.Anonymous;;
ldapConnection.Bind(networkCredential);
//Lets find this person so we can use the correct DN syntax when we authorize them.
SearchRequest FindThem = new SearchRequest();
FindThem.Filter = config.LdapAuth.LdapFilter.Replace("{{Patron}}", Patron);
FindThem.DistinguishedName = config.LdapAuth.LdapDomain;
FindThem.Scope = System.DirectoryServices.Protocols.SearchScope.Subtree;
//We'll execute a search using the bound user
SearchResponse searchresults = (SearchResponse) ldapConnection.SendRequest(FindThem);
//Should only get on result back, if not throw an error
if(searchresults.Entries.Count == 1)
{
SearchResultEntryCollection entries = searchresults.Entries;
SearchResultEntry thispatron = entries[0];
PatronDN = thispatron.DistinguishedName;
}
}
If you wanted to move disabled users to a specific group, from this point you could write logic to check the DistinguishedName of that user, and throw a handled exception if their DistinguishedName contains the name of that group. Also, if the nsAccountLock attribute is available to your binding account as a readable attribute, you could just check the value of that attribute for true, and handle the user accordingly.
Here is the java code for disabling and enabling user in Active Directory using JNDI.
Make sure to connect with your AD before calling below code.
public void disableEnableUser() throws Exception {
ModificationItem[] mods = new ModificationItem[1];
//To enable user
//int UF_ACCOUNT_ENABLE = 0x0001;
//mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl",Integer.toString(UF_ACCOUNT_ENABLE)));
// To disable user
int UF_ACCOUNT_DISABLE = 0x0002;
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl",Integer.toString(UF_ACCOUNT_DISABLE)));
ctx.modifyAttributes("CN=John ABC,OU=Users,OU=anyone,DC=yourcompanyname,DC=com", mods);
}
Distinguished name = "CN=John ABC,OU=Users,OU=anyone,DC=yourcompanyname,DC=com"
This name is depend on your structure of Active Directory, you can confirm from your suport team.
If the directory software supports a password policy feature, it probably provides attributes to lock/deactivate the user. If not, you can simply nullify the password attribute (e.g., userpassword). The LDAP server should return the "inappropriate authentication" error to the client when the authenticated bind is performed.
You could just change the user's password. If it's OpenLDAP with the password-policy overlay, or another LDAP server that supports locking, you can lock the user as well. You really will have to find out.

Adding features to ServiceStack auth provider

I am evaluating ServiceStack using OrmLite. The built in Auth service, along with Session and Cache are so much better than ASP.NET membership provider.
However, out of the box the Auth Service does not provide some of the features required for apps we want to build like:
Change password
Locking of account after 3 unsuccessful logon attempts
Disabling user accounts
Password reminder question and answer
Audit log of log on attempts
Do I need to build custom auth provider or is there something out there which already does provides this functionality?
Many thanks!
I'm just starting to implement a password reset and can see two ways of achieving it (I've not tested - or even tried - either yet):
1.Create a class that inherits from Registration and handles PUT. It should then be possible to call the UpdateUserAuth method of the registration class which would change the password. The problem - for me - here is that the put validation requires username and password to be specified, not just one (We only use email as an identifier). This could be worked around by turning the validation feature off.
2.Create a password reset service that does what UpdateUserAuth does.
var session = this.GetSession();
var existingUser = UserAuthRepo.GetUserAuth(session, null);
if (existingUser == null)
{
throw HttpError.NotFound("User does not exist");
}
var newUserAuth = ToUserAuth(request);
UserAuthRepo.UpdateUserAuth(newUserAuth, existingUser, request.Password);
Obviously need to add some appropriate validation in.
UPDATED
I've put my change password reminder/reset service up as a gist (My first gist!)
here's what I did, works well. - I realise the "new" is a code-smell, just inject it :)
private int LoginAttempts = 0;
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var authRepo = authService.TryResolve<IUserAuthRepository>();
if (authRepo == null)
{
Log.WarnFormat("Tried to authenticate without a registered IUserAuthRepository");
return false;
}
var session = authService.GetSession();
UserAuth userAuth = null;
if (authRepo.TryAuthenticate(userName, password, out userAuth))
{
session.PopulateWith(userAuth);
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.ProviderOAuthAccess = authRepo.GetUserOAuthProviders(session.UserAuthId)
.ConvertAll(x => (IOAuthTokens)x);
return true;
}
else
{
LoginAttempts++;
if (LoginAttempts >= 3)
{
ServiceStack.ServiceInterface.Service s = new Service();
s.Db.ExecuteSql("update [User] set AccountLocked = 'true' where Email='" + userName + "'");
}
authService.RemoveSession();
return false;
}
}
and I hope the mod_from_hell manages to leave this alone!!!

Resources