I am editing a c# WinForm solution and I do not understand the code that gets the user account name. The code is shown below.
The application shows a customized form for each user account and the user account name is needed to get user-specific configuration values from an SQL database.
What happens, to the best I can tell, is the returned user name is correct for the first user account accessed, but after switching to a different user account, the returned user account name is not updated and the initial user account name continues to be returned.
#region "Function to retrieve LoggedIn user"
/// <summary>
/// "Function to retrieve LoggedIn user"
/// </summary>
/// <returns></returns>
private string GetLoggedInUserName()
{
ManagementClass objManClass = new ManagementClass("Win32_Process");
ManagementObjectCollection arrManObjects = objManClass.GetInstances();
foreach (ManagementObject objMan in arrManObjects)
{
if (objMan["Name"].ToString().Trim().ToLower() == "explorer.exe")
{
string[] arrArgs = { "", "" };
try
{
objMan.InvokeMethod("GetOwner", arrArgs);
sUserName = arrArgs[0];
break;
}
catch (Exception lExp)
{
BusinessObject.Logger.Logger.Log(lExp);
}
}
}
return sUserName;
}
#endregion
This application is to run on XP, Vista and 7.
My instinct is to just use something like...
string sUserName = Environment.UserName;
...but my knowledge of the Windows OS is poor and the people who wrote the original code are much smarter than me.
So my two questions are:
(1) Why does this code appear to not update to the new user name when I change user accounts?
(2) why use the 'explore.exe' method instead of simply using 'Environment.UserName'?
Also, two projects in my solution have a GetLoggedInUserName()method. One project runs in the background with a timer that calls the other project, and that project generates the user-customized form.
I have another related question about why the form fails to appear for all user accounts except the admin account that I will post as a separate question once I figure out this question.
If you want the currently logged in user, use can use the WindowsIdentity object:
string currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
The Explorer process is always running when you log onto a Windows box, so it will always be found. If you open Task Manager and view the processes you will see it, and the account that started it. It looks like a throw back to VBScript, although I'm sure that there is an easier way to it with that too.
There is no good reason to use WMI to get the current user account on a local machine over other simpler methods.
For the user name bit try ...
string username = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
Related
We have a mvc application that is using Active Directory to authenticate our users. We are leveraging System.DirectoryServices and using the PricipalContext to authenticate:
_principalContext.ValidateCredentials(userName, pass, ContextOptions.SimpleBind);
However this method only returns a bool and we want to return better messages or even redirect the user to a password reset screen for instances like:
The user is locked out of their account.
The users password is expired.
The user needs to change their password at next login.
So if the user fails to login we call NetValidatePasswordPolicy to see why the user was not able to log in. This seemed to work well but we realized that this method was only returning NET_API_STATUS.NERR_PasswordMustChange no matter what the state of the Active Directory user was.
The only example I have found with this same problem comes from a Sublime Speech plugin here. The code I am using is as follows:
var outputPointer = IntPtr.Zero;
var inputArgs = new NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG { PasswordMatched = false, UserAccountName = username };
inputArgs.ClearPassword = Marshal.StringToBSTR(password);
var inputPointer = IntPtr.Zero;
inputPointer = Marshal.AllocHGlobal(Marshal.SizeOf(inputArgs));
Marshal.StructureToPtr(inputArgs, inputPointer, false);
using (new ComImpersonator(adImpersonatingUserName, adImpersonatingDomainName, adImpersonatingPassword))
{
var status = NetValidatePasswordPolicy(serverName, IntPtr.Zero, NET_VALIDATE_PASSWORD_TYPE.NetValidateAuthentication, inputPointer, ref outputPointer);
if (status == NET_API_STATUS.NERR_Success)
{
var outputArgs = (NET_VALIDATE_OUTPUT_ARG)Marshal.PtrToStructure(outputPointer, typeof(NET_VALIDATE_OUTPUT_ARG));
return outputArgs.ValidationStatus;
}
else
{
//fail
}
}
The code always succeeds so why is the value of outputArgs.ValidationStatus the same result every time regardless of the state of the Active Directory user?
I will break the answer to this question into three different sections:
The Current Problem With Your Methodology
The Issues With Recommended Solutions both Online, and in this Thread
The Solution
The current problem with your methodology.
NetValidatePasswordPolicy requires its InputArgs parameter to take in a pointer to a structure, and the structure you pass in depend on the ValidationType your're passing in. In this case, you are passing NET_VALIDATE_PASSWORD_TYPE.NetValidateAuthentication, which requires an InputArgs of NET_VALIDATE_AUTHENTICATION_INPUT_ARG but you're passing in a pointer to NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG.
Furthermore, you are attempting to assign a "currentPassword' type of value to the NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG structure.
However, there's a bigger fundamental proble to the use of NetValidatePasswordPolicy and that is that you are trying to use this function to validate passwords in Active Directory, but this is not what it is used for. NetValidatePasswordPolicy is used to allow applications to validate against a authentication database provided by the application.
There's more information about NetValidatePasswordPolicy here.
The issues with recommended solutions both online, and in this thread
Various articles online recommend using the LogonUser function found in AdvApi32.dll but this implementation carries its own set of issues:
The first is that LogonUser validates against a local cache, and that means that you will not get immediate accurate information about the account, unless you use the "Network" mode.
The second is that using LogonUser on a Web application, in my opinion is a bit hacky, as it is designed for desktop applications running on client machines. However, considering the limitations provided Microsoft if LogonUser gives desired results, I don't see why it shouldn't be used - barring the caching issues.
Another issue with LogonUser is that how well it works for your use case depends on how your server is configured, for example: There are some particular permissions that need to be enabled on the domain you're authenticating against that need to be in place for 'Network' logon type to work.
More information about LogonUser here.
Also, GetLastError() should not be used, GetLastWin32Error() should be used instead, as it is not safe to use GetLastError().
More information about GetLastWin32Error() here.
The solution.
In order to get an accurate error code from Active Directory, without any caching issues and straight from directory services, this is what needs to be done: rely on COMException coming back from AD when there's an issue with the account, because ultimately, errors is what you're looking for.
First, here's how you trigger an error from Active Directory on authentication of a current user name and a password:
public LdapBindAuthenticationErrors AuthenticateUser(string domain, string username, string password, string ouString)
{
// The path (ouString) should not include the user in the directory, otherwise this will always return true
DirectoryEntry entry = new DirectoryEntry(ouString, username, password);
try
{
// Bind to the native object, this forces authentication.
var obj = entry.NativeObject;
var search = new DirectorySearcher(entry) { Filter = string.Format("({0}={1})", ActiveDirectoryStringConstants.SamAccountName, username) };
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (result != null)
{
return LdapBindAuthenticationErrors.OK;
}
}
catch (DirectoryServicesCOMException c)
{
LdapBindAuthenticationErrors ldapBindAuthenticationError = -1;
// These LDAP bind error codes are found in the "data" piece (string) of the extended error message we are evaluating, so we use regex to pull that string
if (Regex.Match(c.ExtendedErrorMessage, #" data (?<ldapBindAuthenticationError>[a-f0-9]+),").Success)
{
string errorHexadecimal = match.Groups["ldapBindAuthenticationError"].Value;
ldapBindAuthenticationError = (LdapBindAuthenticationErrors)Convert.ToInt32(errorHexadecimal , 16);
return ldapBindAuthenticationError;
}
catch (Exception e)
{
throw;
}
}
return LdapBindAuthenticationErrors.ERROR_LOGON_FAILURE;
}
And these are your "LdapBindAuthenticationErrors", you can find more in MSDN, here.
internal enum LdapBindAuthenticationErrors
{
OK = 0
ERROR_INVALID_PASSWORD = 0x56,
ERROR_PASSWORD_RESTRICTION = 0x52D,
ERROR_LOGON_FAILURE = 0x52e,
ERROR_ACCOUNT_RESTRICTION = 0x52f,
ERROR_INVALID_LOGON_HOURS = 0x530,
ERROR_PASSWORD_EXPIRED = 0x532,
ERROR_ACCOUNT_DISABLED = 0x533,
ERROR_ACCOUNT_EXPIRED = 0x701,
ERROR_PASSWORD_MUST_CHANGE = 0x773,
ERROR_ACCOUNT_LOCKED_OUT = 0x775
}
Then you can use the return type of this Enum and do what you need with it in your controller. The important thing to note, is that you're looking for the "data" piece of the string in the "Extended Error Message" of your COMException because this contains the almighty error code you are hunting for.
Good luck, and I hope this helps. I tested it, and it works great for me.
I have created the registration and login form. Both work perfectly. But how do i recognize the user logged in as the PHP does by using SESSIONS and COOKIES. I can use static class to get data between different pages, but how can i retrieve the logged user data if he closes the application.
Is there any way for achieving this?
Thanks!
I'm assuming that you want something like instant messenger applications like Skype, or cloud storage applications like DropBox, OneDrive or Mega do. They ask you to enter user name and password once, and then start automatically without asking for user's credentials again.
They achieve this by storing user name and password in encrypted format in the file they normally keep in application folder under specific user account. See the following link for details: How can I get the current user directory?
This is standard practice, as another user will not be automatically logged into your app, if they not entered their own credentials.
Make sure you encrypt the user name and password or the whole file before saving it to disk, otherwise it may become an easy target for password stealing malware.
You should use user settings to do this, as this mechanism hides all the necessary work for creating files in the right locations, etc. from the developer. It works fine and it is made for stuff like this.
You design them in Visual Studio in the project properties on the "Settings" tab. Make sure to select the settings type correctly, as application settings are read-only.
Assume you have to settings UserName and UserPassword. Then, in your code, you could do this:
if (String.IsNullOrWhitespace(Properties.Settings.Default.UserName))
{
// USER NEEDS TO LOG IN
string userName;
string password;
if (Login(out userName, out password))
{
try
{
Properties.Settings.Default.UserName = Encrypt(userName);
Properties.Settings.Default.Password = Encrypt(password);
Properties.Settings.Default.Save();
}
catch (Exception exp)
{
...
}
}
}
else
{
// USER IS ALREADY LOGGED IN
}
private bool Login(out string userName, out string password) would be a method that shows a login user interface and returns true on success or false on failure.
private string Encrypt(string input) would be a method to encrypt a string.
I need to get the Display Name of the current user, and cannot find a solution that always works. Just for clarity, I'm not looking for the username. I need the "John Doe". The value displayed on the Start Menu.
There are many posts about this question however none have solved my problem.
Get Windows User Display Name
How do I get the AD Display Name of the currently logged in user
These two posts lead me to:
PrincipalContext context = domain.Equals(Environment.MachineName, StringComparison.CurrentCultureIgnoreCase) ?
new PrincipalContext(ContextType.Machine) :
new PrincipalContext(ContextType.Domain, domain);
UserPrincipal userPrincipal = new UserPrincipal(context) { SamAccountName = username };
PrincipalSearcher searcher = new PrincipalSearcher(userPrincipal);
userPrincipal = searcher.FindOne() as UserPrincipal;
string displayName = userPrincipal.DisplayName;
And this code works for the most part. However if the user's has disabled/stopped the Server service on his/her computer I get an exception saying "The Server service is not started."
System.DirectoryServices.AccountManagement.UserPrincipal.Current.DisplayName
Same error.
How to get logged-in user's full name in windows?
StringBuilder name = new StringBuilder(1024);
uint userNameSize = (uint)name.Capacity;
const int NameDisplay = 3;
GetUserNameEx(NameDisplay, name, ref userNameSize)
Returns no error, but an empty string if the user is not on a domain.
How do you read the user's display (first and last) name on all versions of Windows reliably?
// get SAM compatible name <server/machine>\\<username>
if (0 != GetUserNameEx(2, username, ref userNameSize))
{
IntPtr bufPtr;
try
{
string domain = Regex.Replace(username.ToString(), #"(.+)\\.+", #"$1");
DirectoryContext context = new DirectoryContext(DirectoryContextType.Domain, domain);
DomainController dc = DomainController.FindOne(context);
if (0 == NetUserGetInfo(dc.IPAddress,
Regex.Replace(username.ToString(), #".+\\(.+)", "$1"),
10, out bufPtr))
{
var userInfo = (USER_INFO_10) Marshal.PtrToStructure(bufPtr, typeof (USER_INFO_10));
return Regex.Replace(userInfo.usri10_full_name, #"(\S+), (\S+)", "$2 $1");
}
}
finally
{
NetApiBufferFree(out bufPtr);
}
}
With the above I get an ActiveDirectoryObjectNotFoundException with message "Domain controller not found in the domain.." when DomainController.FindOne is called.
I haven't found a registry setting for the display name.
I'm don't know what else to try. Please help.
All of the above methods will only work if you are on a domain. If you are not, then you must rely on the local user account store. The following details how to retrieve this info: How can I get a list Local Windows Users (Only the Users that appear in the windows Logon Screen). In a domain situation though, the users account will not be in the local store.
If you are on a domain but not connected to the domain controller, the Display Name will not be readily available to you. This information is stored on the domain controller, not the local user's computer. If your users are on a domain, they really shouldn't be able to disable the Server service(use GPOs). Also, they lose much more than the ability to retrieve their user account by disabling that service.
I would check for domain availability before trying to get the display name. If it fails, display a message indicating the failure. There are potentially too many edges cases here to make it work accounting for all of them. Go with the scenario that you intend the program to be used under, and give an error message for the others.
I need some help with examples how to use Credential of a current user running application.
So in windows 7 you can run application using user loged in by simply running application or you can use "Run as a different User" option and run it as another user.
In my Active Directory I have 2 account Domain User and one with Domain Admin rights. I'm login Windows as a Domain User and when I need I'm using "Run as a different User" to launch some task as a Domain Admin.
So the task is to get my Credential and use it to perform some task, lets say rename active directory user name.
Best way to do this as I can see is to ask user running application to enter Domain Admin credential on then start application and use them for various task. Of course I can easily run application with "Run as a different User" but I still need to get this credential and use them.
I've searched through the web and I can't find this, all i could find is using credential for a web auth.
If you can show me some examples how to:
1) Ask user for a Admin user credential ( i can leave without this )
2) Get and use credentials of a user running application
I don't want to know password I know I can't. Don't really want to add to a WPF form password box I prefer to use windows API to handle this i've already entered user name and password using "Run as a different User".
PS: I sorry if this topic exists :( I guess I'm bad at creating correct search requests.
ADDED: to be more clear what I need. In powershell it will look like this:
# This Asks user to enter credentials
$cred = Get-Credential;
# this checks if I have rights to use them.
Get-ADDomain “DOMAIN” –Server “Domain.com” –Credential $cred;
Of course it's simplified as hell though the point is that I can use credentials user entered when ever it's needed.
The equivalent C# to your Get-ADDomain is quite simple, it is just
public void PerformSomeActionAsAdmin(string adminUsername, string adminPassword)
{
//Null causes the constructor to connect to the current domain the machine is on.
// |
// V
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, null, adminUsername, adminPassword))
{
//do something here with ctx, the operations will be performed as whoever's username and password you passed in.
}
}
if you don't want to connect to the current domain and instead want to connect to Domain.com then replace the null with the appropriate string.
EDIT: if you want to use secure strings you can't use System.DirectoryServices.AccountManagement.PrincipalContext, you will need to go with the lower level calls in System.DirectoryServices.Protocols. Doing this process is quite complex, here is a link to the MSDN article "Introduction to System.DirectoryServices.Protocols (S.DS.P)" explaining how to use it. It is a big complex read and honestly I don't think it is worth it to be able to use encrypted strings.
public void PerformSomeActionAsAdmin(NetworkCredential adminCredential)
{
using(LdapConnection connection = new LdapConnection("fabrikam.com", adminCredential))
{
// MAGIC
}
}
Do you want to check if the current user is a doman admin? start by looking at his code, it should help you get started identifying what AD groups the current user is in. This will give you a list of strings that are each group's name the current user belongs to. Then you can check that list against whatever AD group you are trying to check for. Replace YourDomain with your domain name:
WindowsIdentity wi = WindowIdentity.GetCurrent();
List<string> result = new List<string>();
foreach (IdentityReference group in wi.Groups)
{
result.Add(group.Translate(typeof(NTAccount)).ToString().Replace("YourDomain\\", String.Empty));
}
Since i'm not quite sure what you're trying to do, this also might be helpful. You'd have to get the user name and password from a textobx, password box etc. This could be used for an "override" to use, for example, a manager's credentials etc. to do something the current user wasn't allowed to do because of AD group membership etc.
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YourDomain"))
{
if (UserName.Contains("YourDomain\\"))
{
UserName = UserName.Replace("YourDomain\\", String.Empty);
}
//validate the credentials
bool IsValid = pc.ValidateCredentials(UserName, Password);
}
History
We have an elaborate system which ties into DNN on multiple levels for custom user information. Our users are governed under a completely different database than DNN. Because of this, we have special requirements and have created our own membership provider to our database and registration form for our specific needs.
The Problem
In the past, we have used our membership provider and registration form with no problems. Recently, we have encountered a strange situation where the username is mangled after someone attempts to register a new account. It is mangled in such a way that the username becomes the user's Region followed by a hyphen, followed by the user's email address in full. As an example, if a user fills out the form with the following:
username: user
region: CA
email: me#example.com
Before UserController.CreateUser(user) is called, the userInfo.Username equals "user". However, after the call completes, it will be "CA-me#example.com" and the created record in the User table will reflect this change.
Using my custom membership provider (which calls the default AspNetMembershipProvider) verifies that the issue seems to be with AspNetMembershipProvider's CreateUser(ref userInfo); Before this call, the username is "user", but after this call completes it becomes "CA-me#example.com". The result of the call is Success and the user is created in the database. So it doesn't appear to be a failure that is causing the issue.
Settings
I'm not sure if there are settings on DNN which may cause this problem. In my development environment, this entire thing is a non-issue. It only occurs in the live and a secondary internal test environment. Since I'm not able to reproduce this bug in my debug environment, I've considered this might be a site configuration issue. The only option I've found on Site Settings is to make the username the same as the email address, but this is disabled.
I'm going to post my membeship provider code for updating the DNN record, and I'll post the code which creates the user in my register module. I don't know if anyone might need additional code, but I'm willing to provide more where necessary.
Membership Provider
/// <summary>
/// Updates the user in the DNN database.
/// </summary>
/// <param name="userInfo"></param>
private void UpdateDnn(UserInfo userInfo)
{
// _defaultProvider is an instance of AspNetMembershipProvider
if (_defaultProvider.GetUser(userInfo.PortalID, userInfo.UserID) != null)
_defaultProvider.UpdateUser(userInfo);
else
_defaultProvider.CreateUser(ref userInfo);
}
Register Module
public DotNetNuke.Security.Membership.UserCreateStatus Register(Inputs.RegistrationInput input)
{
var userInfo = new UserInfo();
input.Fill(portalId, userInfo); // This copies the user input into the userInfo object.
return UserController.CreateUser(ref userInfo);
}
RegistrationInput.Fill()
public void Fill(int portalId, UserInfo userInfo)
{
if (userInfo == null) throw new ArgumentNullException("userInfo");
userInfo.PortalID = portalId;
userInfo.FirstName = FirstName;
userInfo.LastName = LastName;
userInfo.Username = UserName;
userInfo.DisplayName = DisplayName;
userInfo.Email = Email;
userInfo.Username = UserName;
userInfo.Membership.Password = Password;
userInfo.Profile.Street = Street;
userInfo.Profile.Unit = SuiteApt;
userInfo.Profile.Region = State;
userInfo.Profile.City = City;
userInfo.Profile.PostalCode = Zip;
userInfo.Profile.Telephone = PrimaryPhone;
}
Update
I was digging around in DDN's core for a work around and stumbled upon this (AspNetMembershipProvider.cs:870):
// Check that the OAuth service currently being used for login is the same as was previously used (this should always be true if user authenticated to userid)
if (authUser == null || authUser.AuthenticationType.Equals(service, StringComparison.OrdinalIgnoreCase))
{
isOAuthUser = true;
//DNN-4133 Change username to email address to ensure multiple users with the same email prefix, but different email domains can authenticate
user.Username = service + "-" + user.Email;
}
else
{
createStatus = UserCreateStatus.DuplicateEmail;
}
The line user.Username = service + "-" + user.Email is in the exact format of the username that I am receiving after I call UserController.CreateUser(). The question now is: is this an error in my code or their code? I'm going to keep digging into this, and if nobody answers, I'll try to post an answer to this problem after I understand what is happening here. Furthermore, if I find this to be a bug in their code, I'll post a bug report on DNN's bug tracker and link back to the bug page here.
I finally found the problem. It turns out that the issue is with a line of code above the posted code in the membership provider in the same function:
string service = HttpContext.Current.Request.Params["state"];
The problem with this line is that I pass state via POST as the user's state of residency. DNN seems to consider it the service state. The solution to this is to change the POST input parameter name from state to something else - preferably region as DNN recognizes states as regions. An easy fix, all-in-all, but it took me quite a while to find it. I think DNN should have allowed this parameter to be changed via the state of the membership object or parameter rather than grabbing it directly from the request, but there's not much that can be done on our end in that regard (modifying the core source is strongly discouraged).