//******************************************************************************************************
// UserInfo.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 01/10/2004 - J. Ritchie Carroll
// Original version of source code generated.
// 01/03/2006 - Pinal C. Patel
// 2.0 version of source code migrated from 1.1 source (GSF.Shared.Identity).
// 09/27/2006 - Pinal C. Patel
// Added Authenticate() function.
// 09/29/2006 - Pinal C. Patel
// Added support to impersonate privileged user for retrieving user information.
// 11/06/2007 - Pinal C. Patel
// Modified the logic of Authenticate method to use the user's DirectoryEntry instance.
// Modified UserEntry property to impersonate privileged user if configured for the instance.
// 11/08/2007 - J. Ritchie Carroll
// Implemented user customizable implementation of privileged account credentials.
// 09/15/2008 - J. Ritchie Carroll
// Converted to C#.
// 10/06/2008 - Pinal C. Patel
// Edited code comments.
// 10/06/2008 - Pinal C. Patel
// Added static properties RemoteUserID and RemoteUserInfo.
// 06/18/2009 - Pinal C. Patel
// Modified GetUserProperty() to quit if Enabled is false.
// 07/14/2009 - Pinal C. Patel
// Modified GetUserProperty() to allow information to be retrieved from the logged-on domain only
// in order to prevent timeout issues when trying to retrieve information for a non-domain user.
// 08/06/2009 - Pinal C. Patel
// Enabled upon initialization rather than instantiation.
// 08/12/2009 - Pinal C. Patel
// Fixed issue introduced by marking the type as enabled upon initialization (Initialize()).
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 03/11/2010 - Pinal C. Patel
// Modified AuthenticateUser() to return IPrincipal of the authenticated user instead of boolean.
// 04/07/2010 - Pinal C. Patel
// Updated the timeout prevention logic used in GetUserProperty() to work correctly when
// privileged identity is specified for Active Directory operations.
// 05/24/2010 - Pinal C. Patel
// Added the ability to derive the domain name for added flexibility if one is not specified.
// 05/27/2010 - Pinal C. Patel
// Modified DirectoryEntry object creation in Initialize() to take in to consideration the domain.
// Modified Initialize() to move the derivation of domain name after DirectoryEntry initialization.
// 06/17/2010 - Pinal C. Patel
// Modified the process of getting the user's DirectoryEntry object in Initialize() method to use
// the default constructor of DirectorySearcher resulting in the search for the user to be
// performed in the domain to which the host machine (where this code is executing) is joined.
// 07/12/2010 - Pinal C. Patel
// Added publicly accessible ImpersonatePrivilegedAccount() method allow for the impersonation of
// privileged domain account outside of UserInfo class.
// Added a constructor to allow for the AD search root to be specified.
// 11/04/2010 - Pinal C. Patel
// Modified Initialize() to initialize UserEntry only if the machine is joined to a domain.
// 02/14/2011 - J. Ritchie Carroll
// Added WinNT DirectoryEntry lookups for local accounts when user is not connected to a domain.
// 02/15/2011 - J. Ritchie Carroll
// Modified "Groups" property to always return domain prefixed group names for the user.
// Modified to allow default lookup of account name from machines not connected to a domain even
// if local machine name is not specified as domain name -and- to allow lookup of local account
// information even when connected to a domain if machine name is specified as domain name.
// 02/23/2011 - Pinal C. Patel
// Fixed a issue in the Active Directory lookup of MaximumPasswordAge property.
// 04/04/2011 - J. Ritchie Carroll
// Added Exists and DomainAvailable properties to accommodate testing when domain server is
// not available (such as when laptop is used when not connected to the domain).
// 04/05/2011 - J. Ritchie Carroll
// Updated class to attempt reintialization after system resume in case user no longer has
// access to domain.
// 09/21/2011 - J. Ritchie Carroll
// Added Mono implementation exception regions.
// 11/23/2011 - J. Ritchie Carroll
// Modified to support new life cycle interface requirements (i.e., Disposed event).
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
// 03/05/2013 - Joe France
// Prefer the lastLogonTimestamp property over lastLogon property.
// 03/06/2013 - Pinal C. Patel
// Added new GetUserPropertyValue() and GetUserPropertyValueAsString() methods to replace old
// GetUserProperty() method and marked GetUserPropertyValue() as obsolete.
// Updated LastLogon to revert using lastLogon property instead of lastLogonTimestamp since it is
// updated less frequently and fixed the parsing logic for it since the return value is a large
// integer.
// 06/03/2013 - J. Ritchie Carroll
// Added static local user/group manipulation functions.
// 01/02/2014 - Stephen C. Wills
// Added static account-to-SID conversion functions.
//
//******************************************************************************************************
using System;
using System.Configuration;
using System.Security;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Threading;
using GSF.Configuration;
namespace GSF.Identity
{
///
/// Represents information about a local user or a domain user (e.g., from Active Directory).
///
///
/// See http://msdn.microsoft.com/en-us/library/ms677980.aspx for more information on active directory properties.
///
///
/// This example shows how to retrieve user information from Active Directory:
///
/// using System;
/// using GSF.Identity;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// // Retrieve and display user information from Active Directory.
/// using (UserInfo user = new UserInfo("XYZCorp\\johndoe"))
/// {
/// Console.WriteLine(string.Format("First Name: {0}", user.FirstName));
/// Console.WriteLine(string.Format("Last Name: {0}", user.LastName));
/// Console.WriteLine(string.Format("Middle Initial: {0}", user.MiddleInitial));
/// Console.WriteLine(string.Format("Email Address: {0}", user.Email));
/// Console.WriteLine(string.Format("Telephone Number: {0}", user.Telephone));
/// }
///
/// Console.ReadLine();
/// }
/// }
///
/// This example shows the config file section that can be used to specify the domain account to be used for Active Directory queries:
///
///
///
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
public sealed class UserInfo : ISupportLifecycle, IPersistSettings
{
#region [ Members ]
// Constants
///
/// Specifies the default value for the property.
///
public const bool DefaultPersistSettings = false;
///
/// Specifies the default value for the property.
///
public const string DefaultSettingsCategory = "ActiveDirectory";
internal const string SecurityExceptionFormat = "{0} user account control information cannot be obtained. {1} may not have needed rights to {2}.";
internal const string UnknownErrorFormat = "Unknown error. Invalid value returned when querying user account control for {0}. Value: '{1}'";
internal const int ACCOUNTDISABLED = 2;
internal const int LOCKED = 16;
internal const int PASSWD_CANT_CHANGE = 64;
internal const int DONT_EXPIRE_PASSWORD = 65536;
// Events
///
/// Occurs when the class has been disposed.
///
public event EventHandler Disposed;
// Fields
private IUserInfo m_userInfo;
private string m_domain;
private readonly string m_userName;
private bool m_persistSettings;
private string m_settingsCategory;
private IPrincipal m_passthroughPrincipal;
private int m_userAccountControl;
private bool m_disposed;
internal string m_ldapPath;
internal string m_privilegedDomain;
internal string m_privilegedUserName;
internal string m_privilegedPassword;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of the class.
///
///
/// Login ID in 'domain\username' format of the user's account whose information is to be retrieved. Login ID
/// can also be specified in 'username' format without the domain name, in which case the domain name will be
/// approximated based on the privileged user domain if specified, default logon domain of the host machine
/// if available, or the domain of the identity that owns the host process.
///
/// is a null or empty string.
public UserInfo(string loginID)
: this(loginID, string.Empty)
{
}
///
/// Initializes a new instance of the class.
///
///
/// Login ID in 'domain\username' format of the user's account whose information is to be retrieved. Login ID
/// can also be specified in 'username' format without the domain name, in which case the domain name will be
/// approximated based on the privileged user domain if specified, default logon domain of the host machine
/// if available, or the domain of the identity that owns the host process.
///
///
/// String in 'LDAP://' format that specifies the Active Directory node where search for the user starts.
///
/// is a null or empty string.
public UserInfo(string loginID, string ldapPath)
{
if (string.IsNullOrEmpty(loginID))
throw new ArgumentNullException(nameof(loginID));
string[] accountParts = loginID.Split('\\');
if (accountParts.Length != 2)
{
accountParts = loginID.Split('@');
if (accountParts.Length != 2)
{
// Login ID is specified in 'username' format.
m_userName = loginID;
}
else
{
// Login ID is specified in 'username@domain' format.
m_userName = accountParts[0];
m_domain = accountParts[1];
}
}
else
{
// Login ID is specified in 'domain\username' format.
m_domain = accountParts[0];
m_userName = accountParts[1];
}
m_ldapPath = ldapPath;
// Initialize default settings
m_persistSettings = DefaultPersistSettings;
m_settingsCategory = DefaultSettingsCategory;
m_userAccountControl = -1;
if (Common.IsPosixEnvironment)
m_userInfo = new UnixUserInfo(this);
else
m_userInfo = new WindowsUserInfo(this);
}
///
/// Releases the unmanaged resources before the object is reclaimed by .
///
~UserInfo()
{
Dispose(false);
}
#endregion
#region [ Properties ]
bool ISupportLifecycle.Enabled
{
get
{
return m_userInfo.Enabled;
}
set
{
m_userInfo.Enabled = value;
}
}
///
/// Gets a flag that indicates whether the object has been disposed.
///
bool ISupportLifecycle.IsDisposed
{
get
{
return m_disposed;
}
}
///
/// Gets or sets a boolean value that indicates whether the settings of object are
/// to be saved to the config file.
///
public bool PersistSettings
{
get
{
return m_persistSettings;
}
set
{
m_persistSettings = value;
}
}
///
/// Gets or sets the category under which the settings of object are to be saved
/// to the config file if the property is set to true.
///
/// The value being assigned is null or empty string.
public string SettingsCategory
{
get
{
return m_settingsCategory;
}
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentNullException(nameof(value));
m_settingsCategory = value;
}
}
///
/// Gets or sets the principal used for passthrough authentication.
///
///
/// This is necessary to determine whether a domain user exists when the
/// computer is disconnected from the domain but still able to authenticate
/// using the last seen domain user account.
///
public IPrincipal PassthroughPrincipal
{
get
{
return m_passthroughPrincipal;
}
set
{
m_passthroughPrincipal = value;
}
}
///
/// Gets the domain for the user.
///
public string Domain
{
get
{
return m_domain;
}
internal set
{
m_domain = value;
}
}
///
/// Gets the user name of the user.
///
public string UserName
{
get
{
return m_userName;
}
}
///
/// Gets LDAP path defined for this user, if any.
///
public string LdapPath
{
get
{
return m_ldapPath;
}
}
///
/// Gets the Login ID of the user.
///
public string LoginID
{
get
{
return string.Format("{0}\\{1}", m_domain, m_userName);
}
}
///
/// Gets the ID of the user in LDAP format.
///
public string LdapID
{
get
{
return string.Format("{0}@{1}", m_userName, m_domain);
}
}
///
/// Gets flag that determines if domain is responding to user existence.
///
/// true if domain responds and user exists; otherwise false.
///
/// Note that when the domain is unavailable, this function will return false.
///
public bool DomainRespondsForUser
{
get
{
return m_userInfo.DomainRespondsForUser;
}
}
///
/// Gets flag that determines if user exists.
///
/// true if user is found to exist; otherwise false.
///
///
/// Unlike other properties in this class, will still work even if the domain server is
/// unavailable. Calling this property without access to a domain server will return true if the user
/// is authenticated. This only succeeds if the current user is the same as the , there
/// is no way to determine if a user other than the currently authenticated user exists without access to
/// the domain server. Local accounts are not subject to these constraints since the domain server will
/// effectively be your local computer which is always accessible.
///
///
/// Since this property can check the to determine if the current thread identity
/// is authenticated, it is important that consumers add the following code at application startup:
/// AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
///
///
/// If a laptop user that's normally connected to the domain takes their computer on the road without VPN or other
/// connectivity to the domain, the user can still login to the laptop using cached credentials therefore they are
/// still considered to exist. Without access to the domain, no user information will be available (such as the
/// listing) - if this data is needed offline it will need to be cached by the application in
/// anticipation of offline access.
///
///
public bool Exists
{
get
{
return m_userInfo.Exists;
}
}
///
/// Gets the last login time of the user.
///
public DateTime LastLogon
{
get
{
return m_userInfo.LastLogon;
}
}
///
/// Gets the when the account was created.
///
public DateTime AccountCreationDate
{
get
{
return m_userInfo.AccountCreationDate;
}
}
///
/// Gets the , in UTC, of next password change for the user.
///
public DateTime NextPasswordChangeDate
{
get
{
return m_userInfo.NextPasswordChangeDate;
}
}
///
/// Gets the account control information of the user.
///
/// User account control information cannot be obtained, may not have needed rights.
/// Unknown error. Invalid value returned when querying user account control.
public int UserAccountControl
{
get
{
if (m_userInfo.Enabled && m_userAccountControl == -1)
{
if (m_userInfo.IsLocalAccount)
{
m_userAccountControl = m_userInfo.LocalUserAccountControl;
}
else
{
string userPropertyValue = GetUserPropertyValue("userAccountControl");
if (string.IsNullOrEmpty(userPropertyValue))
throw new SecurityException(string.Format(SecurityExceptionFormat, "Active directory", CurrentUserID, m_domain));
if (!int.TryParse(userPropertyValue, out m_userAccountControl))
throw new InvalidOperationException(string.Format(UnknownErrorFormat, LoginID, userPropertyValue));
}
}
return m_userAccountControl;
}
internal set
{
m_userAccountControl = value;
}
}
///
/// Gets flag that determines if account is locked-out for this user.
///
/// User account control information cannot be obtained, may not have needed rights.
/// Unknown error. Invalid value returned when querying user account control.
public bool AccountIsLockedOut
{
get
{
return Convert.ToBoolean(UserAccountControl & LOCKED);
}
}
///
/// Gets flag that determines if account is disabled for this user.
///
/// User account control information cannot be obtained, may not have needed rights.
/// Unknown error. Invalid value returned when querying user account control.
public bool AccountIsDisabled
{
get
{
return Convert.ToBoolean(UserAccountControl & ACCOUNTDISABLED);
}
}
///
/// Gets flag that determines if account password cannot change for this user.
///
/// User account control information cannot be obtained, may not have needed rights.
/// Unknown error. Invalid value returned when querying user account control.
public bool PasswordCannotChange
{
get
{
return Convert.ToBoolean(UserAccountControl & PASSWD_CANT_CHANGE);
}
}
///
/// Gets flag that determines if account password does not expire for this user.
///
/// User account control information cannot be obtained, may not have needed rights.
/// Unknown error. Invalid value returned when querying user account control.
public bool PasswordDoesNotExpire
{
get
{
return Convert.ToBoolean(UserAccountControl & DONT_EXPIRE_PASSWORD);
}
}
///
/// Gets this maximum password age for the user.
///
public Ticks MaximumPasswordAge
{
get
{
return m_userInfo.MaximumPasswordAge;
}
}
///
/// Gets all the groups associated with the user - this includes local groups and Active Directory groups if applicable.
///
///
/// Groups names are prefixed with their associated domain, computer name or BUILTIN.
///
public string[] Groups
{
get
{
return m_userInfo.Groups;
}
}
///
/// Gets the local groups the user is a member of.
///
///
/// Groups names are prefixed with BUILTIN or computer name.
///
public string[] LocalGroups
{
get
{
return m_userInfo.LocalGroups;
}
}
///
/// Gets the First Name of the user.
///
/// Returns the value retrieved for the "givenName" active directory property.
public string FirstName
{
get
{
if (m_userInfo.IsLocalAccount)
return GetNameElements(DisplayName)[0];
return GetUserPropertyValue("givenName");
}
}
///
/// Gets the Last Name of the user.
///
/// Returns the value retrieved for the "sn" active directory property.
public string LastName
{
get
{
if (m_userInfo.IsLocalAccount)
return GetNameElements(DisplayName)[1];
return GetUserPropertyValue("sn");
}
}
///
/// Gets the Display Name the user.
///
/// Returns the value retrieved for the "displayName" active directory property.
public string DisplayName
{
get
{
if (m_userInfo.IsLocalAccount)
return m_userInfo.FullLocalUserName;
return GetUserPropertyValue("displayName");
}
}
///
/// Gets the Middle Initial of the user.
///
/// Returns the value retrieved for the "initials" active directory property.
public string MiddleInitial
{
get
{
return GetUserPropertyValue("initials");
}
}
///
/// Gets the Full Name of the user.
///
/// Returns the concatenation of , and properties.
public string FullName
{
get
{
if (m_userInfo.IsLocalAccount)
return m_userInfo.FullLocalUserName;
string fName = FirstName;
string lName = LastName;
string mInitial = MiddleInitial;
if (!string.IsNullOrEmpty(fName) && !string.IsNullOrEmpty(lName))
{
if (string.IsNullOrEmpty(mInitial))
return fName + " " + lName;
return fName + " " + mInitial + ". " + lName;
}
return LoginID;
}
}
///
/// Gets the E-Mail address of the user.
///
/// Returns the value retrieved for the "mail" active directory property.
public string Email
{
get
{
return GetUserPropertyValue("mail");
}
}
///
/// Gets the web page address of the user.
///
/// Returns the value retrieved for the "wWWHomePage" active directory property.
public string Webpage
{
get
{
return GetUserPropertyValue("wWWHomePage");
}
}
///
/// Gets the description specified for the user.
///
/// Returns the value retrieved for the "description" active directory property.
public string Description
{
get
{
return GetUserPropertyValue("description");
}
}
///
/// Gets the Telephone Number of the user.
///
/// Returns the value retrieved for the "telephoneNumber" active directory property.
public string Telephone
{
get
{
return GetUserPropertyValue("telephoneNumber");
}
}
///
/// Gets the Title of the user.
///
/// Returns the value retrieved for the "title" active directory property.
public string Title
{
get
{
return GetUserPropertyValue("title");
}
}
///
/// Gets the Company of the user.
///
/// Returns the value retrieved for the "company" active directory property.
public string Company
{
get
{
return GetUserPropertyValue("company");
}
}
///
/// Gets the Office location of the user.
///
/// Returns the value retrieved for the "physicalDeliveryOfficeName" active directory property.
public string Office
{
get
{
return GetUserPropertyValue("physicalDeliveryOfficeName");
}
}
///
/// Gets the Department where the user works.
///
/// Returns the value retrieved for the "department" active directory property.
public string Department
{
get
{
return GetUserPropertyValue("department");
}
}
///
/// Gets the City where the user works.
///
/// Returns the value retrieved for the "l" active directory property.
public string City
{
get
{
return GetUserPropertyValue("l");
}
}
///
/// Gets the Mailbox address of where the user works.
///
/// Returns the value retrieved for the "streetAddress" active directory property.
public string Mailbox
{
get
{
return GetUserPropertyValue("streetAddress");
}
}
///
/// Gets flag that determines if this instance is based on a local account instead of found through LDAP.
///
public bool IsLocalAccount
{
get
{
return m_userInfo.IsLocalAccount;
}
}
#endregion
#region [ Methods ]
///
/// Releases all the resources used by the object.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
private void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
// This will be done regardless of whether the object is finalized or disposed.
if (disposing)
{
if ((object)m_userInfo != null)
m_userInfo.Dispose();
m_userInfo = null;
// This will be done only when the object is disposed by calling Dispose().
SaveSettings();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
if (Disposed != null)
Disposed(this, EventArgs.Empty);
}
}
}
///
/// Initializes the object.
///
/// Failed to initialize directory entry for .
public void Initialize()
{
m_userInfo.Initialize();
if (string.IsNullOrEmpty(m_domain))
m_domain = Environment.MachineName;
}
///
/// Saves settings for the object to the config file if the
/// property is set to true.
///
/// has a value of null or empty string.
public void SaveSettings()
{
if (m_persistSettings)
{
// Ensure that settings category is specified.
if (string.IsNullOrEmpty(m_settingsCategory))
throw new ConfigurationErrorsException("SettingsCategory property has not been set");
// Save settings under the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[m_settingsCategory];
settings["PrivilegedDomain", true].Update(m_privilegedDomain);
settings["PrivilegedUserName", true].Update(m_privilegedUserName);
settings["PrivilegedPassword", true].Update(m_privilegedPassword);
config.Save();
}
}
///
/// Loads saved settings for the object from the config file if the
/// property is set to true.
///
/// has a value of null or empty string.
public void LoadSettings()
{
if (m_persistSettings)
{
// Ensure that settings category is specified.
if (string.IsNullOrEmpty(m_settingsCategory))
throw new ConfigurationErrorsException("SettingsCategory property has not been set");
// Load settings from the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[m_settingsCategory];
settings.Add("PrivilegedDomain", m_privilegedDomain, "Domain of privileged domain user account used for Active Directory information lookup, if needed.");
settings.Add("PrivilegedUserName", m_privilegedUserName, "Username of privileged domain user account used for Active Directory information lookup, if needed.");
settings.Add("PrivilegedPassword", m_privilegedPassword, "Encrypted password of privileged domain user account used for Active Directory information lookup, if needed.", true);
m_privilegedDomain = settings["PrivilegedDomain"].ValueAs(m_privilegedDomain);
m_privilegedUserName = settings["PrivilegedUserName"].ValueAs(m_privilegedUserName);
m_privilegedPassword = settings["PrivilegedPassword"].ValueAs(m_privilegedPassword);
}
}
///
/// Defines the credentials of a privileged domain account that can be used for impersonation prior to the
/// retrieval of user information from the Active Directory.
///
/// Domain of privileged domain user account.
/// Username of privileged domain user account.
/// Password of privileged domain user account.
///
/// This example shows how to define the identity of the user to be used for retrieving information from Active Directory:
///
/// using System;
/// using GSF.Identity;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// using (UserInfo user = new UserInfo("XYZCorp\\johndoe"))
/// {
/// // Define the identity to use for retrieving Active Directory information.
/// // Persist identity credentials encrypted to the config for easy access.
/// user.PersistSettings = true;
/// user.DefinePrivilegedAccount("XYZCorp", "admin", "Passw0rd");
///
/// // Retrieve and display user information from Active Directory.
/// Console.WriteLine(string.Format("First Name: {0}", user.FirstName));
/// Console.WriteLine(string.Format("Last Name: {0}", user.LastName));
/// Console.WriteLine(string.Format("Middle Initial: {0}", user.MiddleInitial));
/// Console.WriteLine(string.Format("Email Address: {0}", user.Email));
/// Console.WriteLine(string.Format("Telephone Number: {0}", user.Telephone));
/// }
///
/// Console.ReadLine();
/// }
/// }
///
///
public void DefinePrivilegedAccount(string domain, string username, string password)
{
// Check input parameters.
if (string.IsNullOrEmpty(domain))
throw new ArgumentNullException(nameof(domain));
if (string.IsNullOrEmpty(username))
throw new ArgumentNullException(nameof(username));
if (string.IsNullOrEmpty(password))
throw new ArgumentNullException(nameof(password));
// Set the credentials for privileged domain user account.
m_privilegedDomain = domain;
m_privilegedUserName = username;
m_privilegedPassword = password;
}
///
/// Impersonates the defined privileged domain account.
///
/// An if privileged domain account has been defined, otherwise null.
public WindowsImpersonationContext ImpersonatePrivilegedAccount()
{
if (!string.IsNullOrEmpty(m_privilegedDomain) &&
!string.IsNullOrEmpty(m_privilegedUserName) &&
!string.IsNullOrEmpty(m_privilegedPassword))
{
// Privileged domain account is specified
return ImpersonateUser(m_privilegedDomain, m_privilegedUserName, m_privilegedPassword);
}
// Privileged domain account is not specified
return null;
}
///
/// Attempts to change the user's password.
///
/// Old password.
/// New password.
public void ChangePassword(string oldPassword, string newPassword)
{
m_userInfo.ChangePassword(oldPassword, newPassword);
}
///
/// Returns the value for specified active directory property.
///
/// Name of the active directory property whose value is to be retrieved.
/// value for the specified active directory property.
public string GetUserPropertyValue(string propertyName)
{
return m_userInfo.GetUserPropertyValue(propertyName);
}
// Split a string into first name and last name
private static string[] GetNameElements(string displayName)
{
displayName = displayName.Trim();
if (!string.IsNullOrEmpty(displayName))
{
int lastSplit = displayName.LastIndexOf(' ');
if (lastSplit >= 0)
return new[] { displayName.Substring(0, lastSplit), displayName.Substring(lastSplit + 1) };
return new[] { displayName, "" };
}
return new[] { "", "" };
}
#endregion
#region [ Static ]
// Static Fields
private static string m_lastUserID;
private static UserInfo s_currentUserInfo;
// Static Properties
///
/// Gets the ID name of the current user.
///
///
/// The ID name returned is that of the user account under which the code is executing.
///
public static string CurrentUserID
{
get
{
try
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
if ((object)identity != null)
return identity.Name;
return null;
}
catch (SecurityException)
{
return null;
}
}
}
///
/// Gets the object for the .
///
public static UserInfo CurrentUserInfo
{
get
{
string currentUserID = CurrentUserID;
if (!string.IsNullOrEmpty(currentUserID))
{
if ((object)s_currentUserInfo == null ||
string.IsNullOrEmpty(m_lastUserID) ||
!currentUserID.Equals(m_lastUserID, StringComparison.OrdinalIgnoreCase))
{
try
{
s_currentUserInfo = new UserInfo(currentUserID);
s_currentUserInfo.Initialize();
}
catch (InitializationException)
{
}
}
}
m_lastUserID = currentUserID;
return s_currentUserInfo;
}
}
///
/// Gets the of the remote web user.
///
///
/// The returned is that of the remote user accessing the web application. This is
/// available only if the virtual directory hosting the web application is configured to use "Integrated
/// Windows Authentication".
///
public static string RemoteUserID
{
get
{
return Thread.CurrentPrincipal.Identity.Name;
}
}
///
/// Gets the object for the .
///
public static UserInfo RemoteUserInfo
{
get
{
string userID = RemoteUserID;
if ((object)userID == null)
return null;
return new UserInfo(RemoteUserID);
}
}
///
/// Gets a boolean value that indicates whether the current machine is joined to a domain (non-local such as AD or LDAP).
///
public static bool MachineIsJoinedToDomain
{
get
{
if (Common.IsPosixEnvironment)
return UnixUserInfo.MachineIsJoinedToDomain;
return WindowsUserInfo.MachineIsJoinedToDomain;
}
}
// Static Methods
///
/// Returns a sorted list of the common built-in local groups. On Windows these groups have a domain name of BUILTIN.
///
/// Sorted list of the common built-in local groups.
///
/// Names in this list will not have a "BUILTIN\" prefix.
///
public static string[] GetBuiltInLocalGroups()
{
if (Common.IsPosixEnvironment)
return UnixUserInfo.GetBuiltInLocalGroups();
return WindowsUserInfo.GetBuiltInLocalGroups();
}
///
/// Authenticates the specified user credentials.
///
/// Domain of user to authenticate.
/// Username of user to authenticate.
/// Password of user to authenticate.
/// true if the user credentials are authenticated successfully; otherwise false.
///
/// This example shows how to validate a user's credentials:
///
/// using System;
/// using GSF.Identity;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// string domain = "XYZCorp";
/// string username = "johndoe";
/// string password = "password";
///
/// // Authenticate user credentials.
/// if ((object)UserInfo.AuthenticateUser(domain, username, password) != null)
/// Console.WriteLine("Successfully authenticated user \"{0}\\{1}\".", domain, username);
/// else
/// Console.WriteLine("Failed to authenticate user \"{0}\\{1}\".", domain, username);
///
/// Console.ReadLine();
/// }
/// }
///
///
public static IPrincipal AuthenticateUser(string domain, string userName, string password)
{
string errorMessage;
return AuthenticateUser(domain, userName, password, out errorMessage);
}
///
/// Authenticates the specified user credentials.
///
/// Domain of user to authenticate.
/// Username of user to authenticate.
/// Password of user to authenticate.
/// Error message returned, if authentication fails.
/// true if the user credentials are authenticated successfully; otherwise false.
///
/// This example shows how to validate a user's credentials and retrieve an error message if validation fails:
///
/// using System;
/// using GSF.Identity;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// string domain = "XYZCorp";
/// string username = "johndoe";
/// string password = "password";
/// string errorMessage;
///
/// // Authenticate user credentials.
/// if ((object)UserInfo.AuthenticateUser(domain, username, password, out errorMessage) != null)
/// Console.WriteLine("Successfully authenticated user \"{0}\\{1}\".", domain, username);
/// else
/// Console.WriteLine("Failed to authenticate user \"{0}\\{1}\" due to exception: {2}", domain, username, errorMessage);
///
/// Console.ReadLine();
/// }
/// }
///
///
public static IPrincipal AuthenticateUser(string domain, string userName, string password, out string errorMessage)
{
if (Common.IsPosixEnvironment)
return UnixUserInfo.AuthenticateUser(domain, userName, password, out errorMessage);
return WindowsUserInfo.AuthenticateUser(domain, userName, password, out errorMessage);
}
///
/// Impersonates the specified user.
///
/// Domain of user to impersonate.
/// Username of user to impersonate.
/// Password of user to impersonate.
/// A object of the impersonated user.
///
/// After impersonating a user the code executes under the impersonated user's identity.
///
///
/// This example shows how to impersonate a user:
///
/// using System;
/// using GSF.Identity;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// Console.WriteLine(string.Format("User before impersonation: {0}", UserInfo.CurrentUserID));
/// UserInfo.ImpersonateUser("XYZCorp", "johndoe", "password"); // Impersonate user.
/// Console.WriteLine(string.Format("User after impersonation: {0}", UserInfo.CurrentUserID));
///
/// Console.ReadLine();
/// }
/// }
///
///
public static WindowsImpersonationContext ImpersonateUser(string domain, string userName, string password)
{
if (Common.IsPosixEnvironment)
return UnixUserInfo.ImpersonateUser(domain, userName, password);
return WindowsUserInfo.ImpersonateUser(domain, userName, password);
}
///
/// Ends the impersonation of the specified user.
///
/// of the impersonated user.
///
/// This example shows how to terminate an active user impersonation:
///
/// using System;
/// using System.IO;
/// using System.Security.Principal;
/// using GSF.Identity;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// // Impersonate user.
/// WindowsImpersonationContext context = UserInfo.ImpersonateUser("XYZCorp", "johndoe", "password");
/// // Perform operation requiring elevated privileges.
/// Console.WriteLine(File.ReadAllText(@"\\server\share\file.xml"));
/// // End the impersonation.
/// UserInfo.EndImpersonation(context);
///
/// Console.ReadLine();
/// }
/// }
///
///
public static void EndImpersonation(WindowsImpersonationContext impersonatedUser)
{
if ((object)impersonatedUser != null)
{
impersonatedUser.Undo();
impersonatedUser.Dispose();
}
}
///
/// Determines if specified is the local domain (i.e., local machine).
///
/// Domain name to check.
/// trueif specified is the local domain (i.e., local machine); otherwise, false.
public static bool IsLocalDomain(string domain)
{
if (Common.IsPosixEnvironment)
return
string.IsNullOrEmpty(domain) ||
domain.Equals(".", StringComparison.Ordinal) ||
domain.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase);
// TODO: NT AUTHORITY and such groups can be localized to the OS language, these groups won't be recognized as local domains on non EN-US machines in this code
return
string.IsNullOrEmpty(domain) ||
domain.Equals(".", StringComparison.Ordinal) ||
domain.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase) ||
domain.Equals("NT SERVICE", StringComparison.OrdinalIgnoreCase) ||
domain.Equals("NT AUTHORITY", StringComparison.OrdinalIgnoreCase) ||
domain.Equals("IIS APPPOOL", StringComparison.OrdinalIgnoreCase);
}
///
/// Determines if local user exists.
///
/// User name to test for existence.
/// true if exists; otherwise, false.
/// was null.
/// No was specified.
public static bool LocalUserExists(string userName)
{
if ((object)userName == null)
throw new ArgumentNullException(nameof(userName));
// Remove any irrelevant white space
userName = userName.Trim();
if (userName.Length == 0)
throw new ArgumentException("No user name was specified.", nameof(userName));
if (Common.IsPosixEnvironment)
return UnixUserInfo.LocalUserExists(userName);
return WindowsUserInfo.LocalUserExists(userName);
}
///
/// Creates a new local user if it does not exist already.
///
/// User name to create if it doesn't exist.
/// Password to user for new user.
/// Optional user description.
/// true if user was created; otherwise, false if user already existed.
/// or was null.
/// No was specified.
/// Could not create local user.
public static bool CreateLocalUser(string userName, string password, string userDescription = null)
{
if ((object)userName == null)
throw new ArgumentNullException(nameof(userName));
if ((object)password == null)
throw new ArgumentNullException(nameof(password));
// Remove any irrelevant white space
userName = userName.Trim();
if (userName.Length == 0)
throw new ArgumentException("Cannot create local user: no user name was specified.", nameof(userName));
if (Common.IsPosixEnvironment)
return UnixUserInfo.CreateLocalUser(userName, password, userDescription);
return WindowsUserInfo.CreateLocalUser(userName, password, userDescription);
}
///
/// Sets local user's password.
///
/// User name to change password for.
/// New password fro user.
/// or was null.
/// No was specified or user does not exist.
/// Could not set password for local user.
public static void SetLocalUserPassword(string userName, string password)
{
if ((object)userName == null)
throw new ArgumentNullException(nameof(userName));
if ((object)password == null)
throw new ArgumentNullException(nameof(password));
// Remove any irrelevant white space
userName = userName.Trim();
if (userName.Length == 0)
throw new ArgumentException("Cannot set password for local user: no user name was specified.", nameof(userName));
if (Common.IsPosixEnvironment)
UnixUserInfo.SetLocalUserPassword(userName, password);
WindowsUserInfo.SetLocalUserPassword(userName, password);
}
///
/// Removes local user if it exists.
///
/// User name to remove if it exists.
/// true if user was removed; otherwise, false if user did not exist.
/// was null.
/// No was specified.
/// Could not remove local user.
public static bool RemoveLocalUser(string userName)
{
if ((object)userName == null)
throw new ArgumentNullException(nameof(userName));
// Remove any irrelevant white space
userName = userName.Trim();
if (userName.Length == 0)
throw new ArgumentException("Cannot remove local user: no user name was specified.", nameof(userName));
if (Common.IsPosixEnvironment)
return UnixUserInfo.RemoveLocalUser(userName);
return WindowsUserInfo.RemoveLocalUser(userName);
}
///
/// Determines if local group exists.
///
/// Group name to test for existence.
/// true if exists; otherwise, false.
/// was null.
/// No was specified.
public static bool LocalGroupExists(string groupName)
{
if ((object)groupName == null)
throw new ArgumentNullException(nameof(groupName));
// Remove any irrelevant white space
groupName = groupName.Trim();
if (groupName.Length == 0)
throw new ArgumentException("No group name was specified.", nameof(groupName));
groupName = ValidateGroupName(groupName);
if (Common.IsPosixEnvironment)
return UnixUserInfo.LocalGroupExists(groupName);
return WindowsUserInfo.LocalGroupExists(groupName);
}
///
/// Creates a new local group if it does not exist already.
///
/// Group name to create if it doesn't exist.
/// Optional group description.
/// true if group was created; otherwise, false if group already existed.
/// was null.
/// No was specified.
/// Could not create local group.
public static bool CreateLocalGroup(string groupName, string groupDescription = null)
{
if ((object)groupName == null)
throw new ArgumentNullException(nameof(groupName));
// Remove any irrelevant white space
groupName = groupName.Trim();
if (groupName.Length == 0)
throw new ArgumentException("Cannot create local group: no group name was specified.", nameof(groupName));
groupName = ValidateGroupName(groupName);
if (Common.IsPosixEnvironment)
return UnixUserInfo.CreateLocalGroup(groupName);
return WindowsUserInfo.CreateLocalGroup(groupName, groupDescription);
}
///
/// Removes local group if it exists.
///
/// Group name to remove if it exists.
/// true if group was removed; otherwise, false if group did not exist.
/// was null.
/// No was specified.
/// Could not remove local group.
public static bool RemoveLocalGroup(string groupName)
{
if ((object)groupName == null)
throw new ArgumentNullException(nameof(groupName));
// Remove any irrelevant white space
groupName = groupName.Trim();
if (groupName.Length == 0)
throw new ArgumentException("Cannot remove local group: no group name was specified.", nameof(groupName));
groupName = ValidateGroupName(groupName);
if (Common.IsPosixEnvironment)
return UnixUserInfo.RemoveLocalGroup(groupName);
return WindowsUserInfo.RemoveLocalGroup(groupName);
}
///
/// Determines if user is in the specified local .
///
/// Group name to test.
/// User name to test.
/// true if user is in group; otherwise, false.
/// or was null.
/// No or was specified.
/// Could not determine if user was in local group.
///
/// This function will handle Windows service virtual accounts by specifying the complete virtual account name,
/// such as @"NT SERVICE\MyService", as the . This function can also detect
/// Active Directory user accounts and groups that may exist in the local group when the
/// is prefixed with a domain name and a backslash "\".
///
public static bool UserIsInLocalGroup(string groupName, string userName)
{
if ((object)groupName == null)
throw new ArgumentNullException(nameof(groupName));
if ((object)userName == null)
throw new ArgumentNullException(nameof(userName));
// Remove any irrelevant white space
groupName = groupName.Trim();
userName = userName.Trim();
if (groupName.Length == 0)
throw new ArgumentException("Cannot determine if user is in local group: no group name was specified.", nameof(groupName));
if (userName.Length == 0)
throw new ArgumentException("Cannot determine if user is in local group: no user name was specified.", nameof(userName));
groupName = ValidateGroupName(groupName);
if (Common.IsPosixEnvironment)
return UnixUserInfo.UserIsInLocalGroup(groupName, userName);
return WindowsUserInfo.UserIsInLocalGroup(groupName, userName);
}
///
/// Adds an existing user to the specified local .
///
/// Group name to add local user to.
/// Existing local user name.
/// true if user was added to local group; otherwise, false meaning user was already in group.
/// or was null.
/// No or was specified.
/// Could not add user to local group.
///
/// This function will handle Windows service virtual accounts by specifying the complete virtual account name,
/// such as @"NT SERVICE\MyService", as the . This function can also add
/// Active Directory user accounts and groups to the local group the when the is
/// prefixed with a domain name and a backslash "\".
///
public static bool AddUserToLocalGroup(string groupName, string userName)
{
if ((object)groupName == null)
throw new ArgumentNullException(nameof(groupName));
if ((object)userName == null)
throw new ArgumentNullException(nameof(userName));
// Remove any irrelevant white space
groupName = groupName.Trim();
userName = userName.Trim();
if (groupName.Length == 0)
throw new ArgumentException("Cannot add user to local group: no group name was specified.", nameof(groupName));
if (userName.Length == 0)
throw new ArgumentException("Cannot add user to local group: no user name was specified.", nameof(userName));
groupName = ValidateGroupName(groupName);
if (Common.IsPosixEnvironment)
return UnixUserInfo.AddUserToLocalGroup(groupName, userName);
return WindowsUserInfo.AddUserToLocalGroup(groupName, userName);
}
///
/// Removes an existing user from the specified local .
///
/// Group name to remove local user from.
/// Existing local user name.
/// true if user was removed from local group; otherwise, false meaning user did not exist in group.
/// or was null.
/// No or was specified.
/// Could not remove user from local group.
///
/// This function will handle Windows service virtual accounts by specifying the complete virtual account name,
/// such as @"NT SERVICE\MyService", as the . This function can also remove
/// Active Directory user accounts and groups from the local group the when the is
/// prefixed with a domain name and a backslash "\".
///
public static bool RemoveUserFromLocalGroup(string groupName, string userName)
{
if ((object)groupName == null)
throw new ArgumentNullException(nameof(groupName));
if ((object)userName == null)
throw new ArgumentNullException(nameof(userName));
// Remove any irrelevant white space
groupName = groupName.Trim();
userName = userName.Trim();
if (groupName.Length == 0)
throw new ArgumentException("Cannot remove user from local group: no group name was specified.", nameof(groupName));
if (userName.Length == 0)
throw new ArgumentException("Cannot remove user from local group: no user name was specified.", nameof(userName));
groupName = ValidateGroupName(groupName);
if (Common.IsPosixEnvironment)
return UnixUserInfo.RemoveUserFromLocalGroup(groupName, userName);
return WindowsUserInfo.RemoveUserFromLocalGroup(groupName, userName);
}
///
/// Gets a list of users that exist in the specified local .
///
/// Group name to remove local user from.
/// List of users that exist in the in group.
/// was null.
/// No was specified.
/// Could not get members for local group.
public static string[] GetLocalGroupUserList(string groupName)
{
if ((object)groupName == null)
throw new ArgumentNullException(nameof(groupName));
// Remove any irrelevant white space
groupName = groupName.Trim();
if (groupName.Length == 0)
throw new ArgumentException("Cannot get members for local group: no group name was specified.", nameof(groupName));
groupName = ValidateGroupName(groupName);
if (Common.IsPosixEnvironment)
return UnixUserInfo.GetLocalGroupUserList(groupName);
return WindowsUserInfo.GetLocalGroupUserList(groupName);
}
///
/// Converts the given user name to the SID corresponding to that name.
///
/// The user name for which to look up the SID.
/// The SID for the given user name, or the user name if no SID can be found.
///
/// If the cannot be converted to a SID,
/// will be the return value.
///
public static string UserNameToSID(string userName)
{
if (Common.IsPosixEnvironment)
return UnixUserInfo.UserNameToSID(userName);
return WindowsUserInfo.AccountNameToSID(userName);
}
///
/// Converts the given group name to the SID corresponding to that name.
///
/// The group name for which to look up the SID.
/// The SID for the given group name, or the group name if no SID can be found.
///
/// If the cannot be converted to a SID,
/// will be the return value.
///
public static string GroupNameToSID(string groupName)
{
if (Common.IsPosixEnvironment)
return UnixUserInfo.GroupNameToSID(groupName);
return WindowsUserInfo.AccountNameToSID(groupName);
}
///
/// Converts the given SID to the corresponding account name.
///
/// The SID for which to look up the account name.
/// The account name for the given SID, or the SID if no account name can be found.
///
/// If the cannot be converted to an account name,
/// will be the return value.
///
public static string SIDToAccountName(string sid)
{
if (Common.IsPosixEnvironment)
return UnixUserInfo.SIDToAccountName(sid);
return WindowsUserInfo.SIDToAccountName(sid);
}
///
/// Determines whether the given security identifier identifies a user account.
///
/// The security identifier.
/// True if the security identifier identifies a user account; false otherwise.
public static bool IsUserSID(string sid)
{
if (Common.IsPosixEnvironment)
return UnixUserInfo.IsUserSID(sid);
return WindowsUserInfo.IsUserSID(sid);
}
///
/// Determines whether the given security identifier identifies a group.
///
/// The security identifier.
/// True if the security identifier identifies a group; false otherwise.
public static bool IsGroupSID(string sid)
{
if (Common.IsPosixEnvironment)
return UnixUserInfo.IsGroupSID(sid);
return WindowsUserInfo.IsGroupSID(sid);
}
// DirectoryEntry will only resolve "BUILTIN\" groups with a dot ".\"
internal static string ValidateGroupName(string groupName)
{
if (!Common.IsPosixEnvironment && groupName.StartsWith(@"BUILTIN\", StringComparison.OrdinalIgnoreCase))
return Regex.Replace(groupName, @"^BUILTIN\\", @".\", RegexOptions.IgnoreCase);
return groupName;
}
#endregion
}
}