//******************************************************************************************************// LdapSecurityProvider.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:// ----------------------------------------------------------------------------------------------------// 07/08/2010 - Pinal C. Patel// Generated original version of source code.// 12/03/2010 - Pinal C. Patel// Override the default behavior of TranslateRole() to translate a SID to its role name.// 01/05/2011 - Pinal C. Patel// Added overrides to RefreshData(), UpdateData(), ResetPassword() and ChangePassword() methods.// 02/14/2011 - J. Ritchie Carroll// Modified provider to be able to use local accounts when user is not connected to a domain.// 06/09/2011 - Pinal C. Patel// Fixed a issue in the caching logic of RefreshData() method.// 08/16/2011 - Pinal C. Patel// Made offline caching of user data for authentication purpose optional and turned on by default.// 12/20/2012 - Starlynn Danyelle Gilliam// Modified Header.// 03/08/2013 - Pinal C. Patel// Modified to enabled persistence on UserInfo only temporarily prior to calling Initialize() to // load privileged user credentials if specified. This is to prevent any accidental updates to the // config file when object gets disposed which would cause a web application to restart. ////****************************************************************************************************** using System;using System.Collections.Generic;using System.Linq;using System.Reflection;using System.Security;using System.Security.Principal;using GSF.Configuration;using GSF.Identity; namespace GSF.Security{ /// <summary> /// Represents an <see cref="ISecurityProvider"/> that uses Active Directory for its backend data store and credential authentication. /// </summary> /// <remarks> /// A <a href="http://en.wikipedia.org/wiki/Security_Identifier" target="_blank">Security Identifier</a> can also be specified in /// <b>IncludedResources</b> instead of a role name in the format of 'SID:<Security Identifier>' (Example: SID:S-1-5-21-19610888-1443184010-1631745340-269783). /// </remarks> /// <example> /// Required config file entries: /// <code> /// <![CDATA[ /// <?xml version="1.0"?> /// <configuration> /// <configSections> /// <section name="categorizedSettings" type="GSF.Configuration.CategorizedSettingsSection, GSF.Core" /> /// </configSections> /// <categorizedSettings> /// <securityProvider> /// <add name="ApplicationName" value="" description="Name of the application being secured as defined in the backend security datastore." /// encrypted="false" /> /// <add name="ConnectionString" value="LDAP://DC=COMPANY,DC=COM" description="Connection string to be used for connection to the backend security data store." /// encrypted="false" /> /// <add name="ProviderType" value="GSF.Security.LdapSecurityProvider, GSF.Security" description="The type to be used for enforcing security." /// encrypted="false" /> /// <add name="UserCacheTimeout" value="5" description="Defines the timeout, in whole minutes, for a user's provider cache. Any value less than 1 will cause cache reset every minute." /// encrypted="false" /> /// <add name="IncludedResources" value="*=*" description="Semicolon delimited list of resources to be secured along with role names." /// encrypted="false" /> /// <add name="ExcludedResources" value="" description="Semicolon delimited list of resources to be excluded from being secured." /// encrypted="false" /> /// <add name="NotificationSmtpServer" value="localhost" description="SMTP server to be used for sending out email notification messages." /// encrypted="false" /> /// <add name="NotificationSenderEmail" value="sender@company.com" description="Email address of the sender of email notification messages." /// encrypted="false" /> /// <add name="EnableOfflineCaching" value="True" description="True to enable caching of user information for authentication in offline state, otherwise False." /// encrypted="false" /> /// <add name="CacheRetryDelayInterval" value="200" description="Wait interval, in milliseconds, before retrying load of user data cache." /// encrypted="false" /> /// <add name="CacheMaximumRetryAttempts" value="10" description="Maximum retry attempts allowed for loading user data cache." /// encrypted="false" /> /// </securityProvider> /// <activeDirectory> /// <add name="PrivilegedDomain" value="" description="Domain of privileged domain user account." /// encrypted="false" /> /// <add name="PrivilegedUserName" value="" description="Username of privileged domain user account." /// encrypted="false" /> /// <add name="PrivilegedPassword" value="" description="Password of privileged domain user account." /// encrypted="true" /> /// </activeDirectory> /// </categorizedSettings> /// </configuration> /// ]]> /// </code> /// </example> public class LdapSecurityProvider : SecurityProviderBase { #region [ Members ] // Constants /// <summary> /// Defines the provider ID for the <see cref="LdapSecurityProvider"/>. /// </summary> public const int ProviderID = 0; /// <summary> /// Specifies the default value for the <see cref="EnableOfflineCaching"/> property. /// </summary> public const bool DefaultEnableOfflineCaching = true; /// <summary> /// Specifies the default value for the <see cref="CacheRetryDelayInterval"/> property. /// </summary> public const double DefaultCacheRetryDelayInterval = 1000.0D; /// <summary> /// Specifies the default value for the <see cref="CacheMaximumRetryAttempts"/> property. /// </summary> public const int DefaultCacheMaximumRetryAttempts = 5; // Fields private bool m_enableOfflineCaching; private double m_cacheRetryDelayInterval; private int m_cacheMaximumRetryAttempts; private WindowsPrincipal m_windowsPrincipal; #endregion #region [ Constructors ] /// <summary> /// Initializes a new instance of the <see cref="LdapSecurityProvider"/> class. /// </summary> /// <param name="username">Name that uniquely identifies the user.</param> public LdapSecurityProvider(string username) : this(username, true, false, true) { } /// <summary> /// Initializes a new instance of the <see cref="LdapSecurityProvider"/> class. /// </summary> /// <param name="username">Name that uniquely identifies the user.</param> /// <param name="canRefreshData">true if the security provider can refresh <see cref="UserData"/> from the backend data store, otherwise false.</param> /// <param name="canResetPassword">true if the security provider can reset user password, otherwise false.</param> /// <param name="canChangePassword">true if the security provider can change user password, otherwise false.</param> protected LdapSecurityProvider(string username, bool canRefreshData, bool canResetPassword, bool canChangePassword) : base(username, canRefreshData, canResetPassword, canChangePassword) { m_enableOfflineCaching = DefaultEnableOfflineCaching; m_cacheRetryDelayInterval = DefaultCacheRetryDelayInterval; m_cacheMaximumRetryAttempts = DefaultCacheMaximumRetryAttempts; } #endregion #region [ Properties ] /// <summary> /// Gets or sets a boolean value that indicates whether user information is to be cached for offline authentication. /// </summary> public bool EnableOfflineCaching { get { return m_enableOfflineCaching; } set { m_enableOfflineCaching = value; } } /// <summary> /// Gets or sets the wait interval (in milliseconds) before retrying load of offline user data cache. /// </summary> public double CacheRetryDelayInterval { get { return m_cacheRetryDelayInterval; } set { m_cacheRetryDelayInterval = value; } } /// <summary> /// Gets or sets the maximum retry attempts allowed for loading offline user data cache. /// </summary> public int CacheMaximumRetryAttempts { get { return m_cacheMaximumRetryAttempts; } set { m_cacheMaximumRetryAttempts = value; } } /// <summary> /// Gets the original <see cref="WindowsPrincipal"/> of the user if the user exists in Active Directory. /// </summary> public WindowsPrincipal WindowsPrincipal { get { return m_windowsPrincipal; } protected set { m_windowsPrincipal = value; } } #endregion #region [ Methods ] #region [ Not Supported ] /// <summary> /// Resets user password in the backend data store. /// </summary> /// <param name="securityAnswer">Answer to the user's security question.</param> /// <returns>true if the password is reset, otherwise false.</returns> /// <exception cref="NotSupportedException">Always</exception> public override bool ResetPassword(string securityAnswer) { throw new NotSupportedException(); } #endregion /// <summary> /// Saves <see cref="LdapSecurityProvider"/> settings to the config file if the <see cref="SecurityProviderBase.PersistSettings"/> property is set to true. /// </summary> public override void SaveSettings() { base.SaveSettings(); if (PersistSettings) { // Save settings under the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; settings["EnableOfflineCaching", true].Update(m_enableOfflineCaching); settings["CacheRetryDelayInterval", true].Update(m_cacheRetryDelayInterval); settings["CacheMaximumRetryAttempts", true].Update(m_cacheMaximumRetryAttempts); config.Save(); } } /// <summary> /// Loads saved <see cref="LdapSecurityProvider"/> settings from the config file if the <see cref="SecurityProviderBase.PersistSettings"/> property is set to true. /// </summary> public override void LoadSettings() { base.LoadSettings(); if (PersistSettings) { // Load settings from the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; settings.Add("EnableOfflineCaching", m_enableOfflineCaching, "True to enable caching of user group information for authentication in offline state, otherwise False."); settings.Add("CacheRetryDelayInterval", m_cacheRetryDelayInterval, "Wait interval, in milliseconds, before retrying load of user data cache."); settings.Add("CacheMaximumRetryAttempts", m_cacheMaximumRetryAttempts, "Maximum retry attempts allowed for loading user data cache."); EnableOfflineCaching = settings["EnableOfflineCaching"].ValueAs(m_enableOfflineCaching); CacheRetryDelayInterval = settings["CacheRetryDelayInterval"].ValueAs(m_cacheRetryDelayInterval); CacheMaximumRetryAttempts = settings["CacheMaximumRetryAttempts"].ValueAs(m_cacheMaximumRetryAttempts); } } /// <summary> /// Authenticates the user. /// </summary> /// <returns>true if the user is authenticated, otherwise false.</returns> public override bool Authenticate() { // Check prerequisites bool isValid = (UserData.IsDefined && !UserData.IsDisabled && !UserData.IsLockedOut) && (UserData.PasswordChangeDateTime == DateTime.MinValue || UserData.PasswordChangeDateTime > DateTime.UtcNow); if (!isValid) return false; if (string.IsNullOrEmpty(Password)) { // Validate with passthrough credentials m_windowsPrincipal = PassthroughPrincipal as WindowsPrincipal; IsUserAuthenticated = (object)m_windowsPrincipal != null && ((!string.IsNullOrEmpty(UserData.LoginID) && m_windowsPrincipal.Identity.Name.Equals(UserData.LoginID, StringComparison.OrdinalIgnoreCase)) || (!string.IsNullOrEmpty(UserData.Username) && m_windowsPrincipal.Identity.Name.Equals(UserData.Username, StringComparison.OrdinalIgnoreCase))) && m_windowsPrincipal.Identity.IsAuthenticated; } else { // Validate by performing network logon string[] userParts = UserData.LoginID.Split('\\'); m_windowsPrincipal = UserInfo.AuthenticateUser(userParts[0], userParts[1], Password) as WindowsPrincipal; IsUserAuthenticated = (object)m_windowsPrincipal != null && m_windowsPrincipal.Identity.IsAuthenticated; } return IsUserAuthenticated; } /// <summary> /// Refreshes the <see cref="UserData"/> from the backend data store. /// </summary> /// <returns>true if <see cref="SecurityProviderBase.UserData"/> is refreshed, otherwise false.</returns> public override bool RefreshData() { // For consistency with WindowIdentity principal, user groups are loaded into Roles collection UserData userData = new UserData(UserData.Username); bool result = RefreshData(userData, userData.Roles, ProviderID); if (result) { string[] parts; // Remove domain name prefixes from user group names (again to match WindowIdentity principal implementation) for (int i = 0; i < userData.Roles.Count; i++) { parts = userData.Roles[i].Split('\\'); if (parts.Length == 2) userData.Roles[i] = parts[1]; } } UserData = userData; return result; } /// <summary> /// Refreshes the <see cref="UserData"/> from the backend data store loading user groups into desired collection. /// </summary> /// <param name="userData">The structure for the data being refreshed.</param> /// <param name="groupCollection">Target collection for user groups.</param> /// <param name="providerID">Unique provider ID used to distinguish cached user data that may be different based on provider.</param> /// <returns>true if <see cref="SecurityProviderBase.UserData"/> is refreshed, otherwise false.</returns> protected virtual bool RefreshData(UserData userData, List<string> groupCollection, int providerID) { if ((object)groupCollection == null) throw new ArgumentNullException(nameof(groupCollection)); if (string.IsNullOrEmpty(userData.Username)) return false; // Initialize user data userData.Initialize(); // Populate user data UserInfo user = null; UserDataCache userDataCache = null; try { // Get current local user data cache if (m_enableOfflineCaching) { userDataCache = UserDataCache.GetCurrentCache(providerID); userDataCache.RetryDelayInterval = m_cacheRetryDelayInterval; userDataCache.MaximumRetryAttempts = m_cacheMaximumRetryAttempts; userDataCache.ReloadOnChange = false; userDataCache.AutoSave = true; userDataCache.Load(); } // Create user info object using specified LDAP path if provided string ldapPath = GetLdapPath(); if (string.IsNullOrEmpty(ldapPath)) user = new UserInfo(userData.Username); else user = new UserInfo(userData.Username, ldapPath); // Make sure to load privileged user credentials from config file if present. user.PersistSettings = true; // If the system is currently logged in under a domain account and the system is disconnected from the domain, // UserInfo will need access to the passthrough principal to determine whether the user exists on the domain user.PassthroughPrincipal = PassthroughPrincipal; // Attempt to determine if user exists (this will initialize user object if not initialized already) userData.IsDefined = user.Exists; userData.LoginID = user.LoginID; // Fill in user information from domain data if it is available if (!user.DomainRespondsForUser) { // Attempt to load previously cached user information when domain is offline UserData cachedUserData; if ((object)userDataCache != null && userDataCache.TryGetUserData(userData.LoginID, out cachedUserData)) { // Copy relevant cached user information userData.IsDefined = true; userData.FirstName = cachedUserData.FirstName; userData.LastName = cachedUserData.LastName; userData.CompanyName = cachedUserData.CompanyName; userData.PhoneNumber = cachedUserData.PhoneNumber; userData.EmailAddress = cachedUserData.EmailAddress; userData.IsLockedOut = cachedUserData.IsLockedOut; userData.IsDisabled = cachedUserData.IsDisabled; userData.Roles.AddRange(cachedUserData.Roles); userData.Groups.AddRange(cachedUserData.Groups); // If domain is offline, a password change cannot be initiated userData.PasswordChangeDateTime = DateTime.MaxValue; userData.AccountCreatedDateTime = cachedUserData.AccountCreatedDateTime; } else { // No previous user data was cached and domain is currently unavailable, however Windows could // allow authentication using cached credentials when authenticating, so all we can do at this // point is assume that user "could" exist and allow authentication to be attempted userData.IsDefined = true; userData.IsLockedOut = false; userData.IsDisabled = false; userData.PasswordChangeDateTime = DateTime.MaxValue; userData.AccountCreatedDateTime = DateTime.MinValue; } } else if (userData.IsDefined) { // Copy relevant user information userData.FirstName = user.FirstName; userData.LastName = user.LastName; userData.CompanyName = user.Company; userData.PhoneNumber = user.Telephone; userData.EmailAddress = user.Email; try { userData.IsLockedOut = user.AccountIsLockedOut; userData.IsDisabled = user.AccountIsDisabled; } catch (SecurityException) { // AD may restrict information on account availability, if so, have to make a safe assumption: userData.IsLockedOut = true; userData.IsDisabled = true; } userData.PasswordChangeDateTime = user.NextPasswordChangeDate; userData.AccountCreatedDateTime = user.AccountCreationDate; // Assign all groups the user is a member of foreach (string groupName in user.Groups) { if (!groupCollection.Contains(groupName, StringComparer.OrdinalIgnoreCase)) groupCollection.Add(groupName); } if ((object)userDataCache != null) { // Cache user data so that information can be loaded later if domain is unavailable userDataCache[userData.LoginID] = userData; // Wait for pending serialization since cache is scoped locally to this method and will be disposed before exit userDataCache.WaitForSave(); } } return userData.IsDefined; } finally { if ((object)user != null) { user.PersistSettings = false; user.Dispose(); } if ((object)userDataCache != null) userDataCache.Dispose(); } } /// <summary> /// Changes user password in the backend data store.Lines 1–500 of 604. Load more
//****************************************************************************************************** // LdapSecurityProvider.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: // ---------------------------------------------------------------------------------------------------- // 07/08/2010 - Pinal C. Patel // Generated original version of source code. // 12/03/2010 - Pinal C. Patel // Override the default behavior of TranslateRole() to translate a SID to its role name. // 01/05/2011 - Pinal C. Patel // Added overrides to RefreshData(), UpdateData(), ResetPassword() and ChangePassword() methods. // 02/14/2011 - J. Ritchie Carroll // Modified provider to be able to use local accounts when user is not connected to a domain. // 06/09/2011 - Pinal C. Patel // Fixed a issue in the caching logic of RefreshData() method. // 08/16/2011 - Pinal C. Patel // Made offline caching of user data for authentication purpose optional and turned on by default. // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // 03/08/2013 - Pinal C. Patel // Modified to enabled persistence on UserInfo only temporarily prior to calling Initialize() to // load privileged user credentials if specified. This is to prevent any accidental updates to the // config file when object gets disposed which would cause a web application to restart. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Security; using System.Security.Principal; using GSF.Configuration; using GSF.Identity; namespace GSF.Security { /// <summary> /// Represents an <see cref="ISecurityProvider"/> that uses Active Directory for its backend data store and credential authentication. /// </summary> /// <remarks> /// A <a href="http://en.wikipedia.org/wiki/Security_Identifier" target="_blank">Security Identifier</a> can also be specified in /// <b>IncludedResources</b> instead of a role name in the format of 'SID:<Security Identifier>' (Example: SID:S-1-5-21-19610888-1443184010-1631745340-269783). /// </remarks> /// <example> /// Required config file entries: /// <code> /// <![CDATA[ /// <?xml version="1.0"?> /// <configuration> /// <configSections> /// <section name="categorizedSettings" type="GSF.Configuration.CategorizedSettingsSection, GSF.Core" /> /// </configSections> /// <categorizedSettings> /// <securityProvider> /// <add name="ApplicationName" value="" description="Name of the application being secured as defined in the backend security datastore." /// encrypted="false" /> /// <add name="ConnectionString" value="LDAP://DC=COMPANY,DC=COM" description="Connection string to be used for connection to the backend security data store." /// encrypted="false" /> /// <add name="ProviderType" value="GSF.Security.LdapSecurityProvider, GSF.Security" description="The type to be used for enforcing security." /// encrypted="false" /> /// <add name="UserCacheTimeout" value="5" description="Defines the timeout, in whole minutes, for a user's provider cache. Any value less than 1 will cause cache reset every minute." /// encrypted="false" /> /// <add name="IncludedResources" value="*=*" description="Semicolon delimited list of resources to be secured along with role names." /// encrypted="false" /> /// <add name="ExcludedResources" value="" description="Semicolon delimited list of resources to be excluded from being secured." /// encrypted="false" /> /// <add name="NotificationSmtpServer" value="localhost" description="SMTP server to be used for sending out email notification messages." /// encrypted="false" /> /// <add name="NotificationSenderEmail" value="sender@company.com" description="Email address of the sender of email notification messages." /// encrypted="false" /> /// <add name="EnableOfflineCaching" value="True" description="True to enable caching of user information for authentication in offline state, otherwise False." /// encrypted="false" /> /// <add name="CacheRetryDelayInterval" value="200" description="Wait interval, in milliseconds, before retrying load of user data cache." /// encrypted="false" /> /// <add name="CacheMaximumRetryAttempts" value="10" description="Maximum retry attempts allowed for loading user data cache." /// encrypted="false" /> /// </securityProvider> /// <activeDirectory> /// <add name="PrivilegedDomain" value="" description="Domain of privileged domain user account." /// encrypted="false" /> /// <add name="PrivilegedUserName" value="" description="Username of privileged domain user account." /// encrypted="false" /> /// <add name="PrivilegedPassword" value="" description="Password of privileged domain user account." /// encrypted="true" /> /// </activeDirectory> /// </categorizedSettings> /// </configuration> /// ]]> /// </code> /// </example> public class LdapSecurityProvider : SecurityProviderBase { #region [ Members ] // Constants /// <summary> /// Defines the provider ID for the <see cref="LdapSecurityProvider"/>. /// </summary> public const int ProviderID = 0; /// <summary> /// Specifies the default value for the <see cref="EnableOfflineCaching"/> property. /// </summary> public const bool DefaultEnableOfflineCaching = true; /// <summary> /// Specifies the default value for the <see cref="CacheRetryDelayInterval"/> property. /// </summary> public const double DefaultCacheRetryDelayInterval = 1000.0D; /// <summary> /// Specifies the default value for the <see cref="CacheMaximumRetryAttempts"/> property. /// </summary> public const int DefaultCacheMaximumRetryAttempts = 5; // Fields private bool m_enableOfflineCaching; private double m_cacheRetryDelayInterval; private int m_cacheMaximumRetryAttempts; private WindowsPrincipal m_windowsPrincipal; #endregion #region [ Constructors ] /// <summary> /// Initializes a new instance of the <see cref="LdapSecurityProvider"/> class. /// </summary> /// <param name="username">Name that uniquely identifies the user.</param> public LdapSecurityProvider(string username) : this(username, true, false, true) { } /// <summary> /// Initializes a new instance of the <see cref="LdapSecurityProvider"/> class. /// </summary> /// <param name="username">Name that uniquely identifies the user.</param> /// <param name="canRefreshData">true if the security provider can refresh <see cref="UserData"/> from the backend data store, otherwise false.</param> /// <param name="canResetPassword">true if the security provider can reset user password, otherwise false.</param> /// <param name="canChangePassword">true if the security provider can change user password, otherwise false.</param> protected LdapSecurityProvider(string username, bool canRefreshData, bool canResetPassword, bool canChangePassword) : base(username, canRefreshData, canResetPassword, canChangePassword) { m_enableOfflineCaching = DefaultEnableOfflineCaching; m_cacheRetryDelayInterval = DefaultCacheRetryDelayInterval; m_cacheMaximumRetryAttempts = DefaultCacheMaximumRetryAttempts; } #endregion #region [ Properties ] /// <summary> /// Gets or sets a boolean value that indicates whether user information is to be cached for offline authentication. /// </summary> public bool EnableOfflineCaching { get { return m_enableOfflineCaching; } set { m_enableOfflineCaching = value; } } /// <summary> /// Gets or sets the wait interval (in milliseconds) before retrying load of offline user data cache. /// </summary> public double CacheRetryDelayInterval { get { return m_cacheRetryDelayInterval; } set { m_cacheRetryDelayInterval = value; } } /// <summary> /// Gets or sets the maximum retry attempts allowed for loading offline user data cache. /// </summary> public int CacheMaximumRetryAttempts { get { return m_cacheMaximumRetryAttempts; } set { m_cacheMaximumRetryAttempts = value; } } /// <summary> /// Gets the original <see cref="WindowsPrincipal"/> of the user if the user exists in Active Directory. /// </summary> public WindowsPrincipal WindowsPrincipal { get { return m_windowsPrincipal; } protected set { m_windowsPrincipal = value; } } #endregion #region [ Methods ] #region [ Not Supported ] /// <summary> /// Resets user password in the backend data store. /// </summary> /// <param name="securityAnswer">Answer to the user's security question.</param> /// <returns>true if the password is reset, otherwise false.</returns> /// <exception cref="NotSupportedException">Always</exception> public override bool ResetPassword(string securityAnswer) { throw new NotSupportedException(); } #endregion /// <summary> /// Saves <see cref="LdapSecurityProvider"/> settings to the config file if the <see cref="SecurityProviderBase.PersistSettings"/> property is set to true. /// </summary> public override void SaveSettings() { base.SaveSettings(); if (PersistSettings) { // Save settings under the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; settings["EnableOfflineCaching", true].Update(m_enableOfflineCaching); settings["CacheRetryDelayInterval", true].Update(m_cacheRetryDelayInterval); settings["CacheMaximumRetryAttempts", true].Update(m_cacheMaximumRetryAttempts); config.Save(); } } /// <summary> /// Loads saved <see cref="LdapSecurityProvider"/> settings from the config file if the <see cref="SecurityProviderBase.PersistSettings"/> property is set to true. /// </summary> public override void LoadSettings() { base.LoadSettings(); if (PersistSettings) { // Load settings from the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; settings.Add("EnableOfflineCaching", m_enableOfflineCaching, "True to enable caching of user group information for authentication in offline state, otherwise False."); settings.Add("CacheRetryDelayInterval", m_cacheRetryDelayInterval, "Wait interval, in milliseconds, before retrying load of user data cache."); settings.Add("CacheMaximumRetryAttempts", m_cacheMaximumRetryAttempts, "Maximum retry attempts allowed for loading user data cache."); EnableOfflineCaching = settings["EnableOfflineCaching"].ValueAs(m_enableOfflineCaching); CacheRetryDelayInterval = settings["CacheRetryDelayInterval"].ValueAs(m_cacheRetryDelayInterval); CacheMaximumRetryAttempts = settings["CacheMaximumRetryAttempts"].ValueAs(m_cacheMaximumRetryAttempts); } } /// <summary> /// Authenticates the user. /// </summary> /// <returns>true if the user is authenticated, otherwise false.</returns> public override bool Authenticate() { // Check prerequisites bool isValid = (UserData.IsDefined && !UserData.IsDisabled && !UserData.IsLockedOut) && (UserData.PasswordChangeDateTime == DateTime.MinValue || UserData.PasswordChangeDateTime > DateTime.UtcNow); if (!isValid) return false; if (string.IsNullOrEmpty(Password)) { // Validate with passthrough credentials m_windowsPrincipal = PassthroughPrincipal as WindowsPrincipal; IsUserAuthenticated = (object)m_windowsPrincipal != null && ((!string.IsNullOrEmpty(UserData.LoginID) && m_windowsPrincipal.Identity.Name.Equals(UserData.LoginID, StringComparison.OrdinalIgnoreCase)) || (!string.IsNullOrEmpty(UserData.Username) && m_windowsPrincipal.Identity.Name.Equals(UserData.Username, StringComparison.OrdinalIgnoreCase))) && m_windowsPrincipal.Identity.IsAuthenticated; } else { // Validate by performing network logon string[] userParts = UserData.LoginID.Split('\\'); m_windowsPrincipal = UserInfo.AuthenticateUser(userParts[0], userParts[1], Password) as WindowsPrincipal; IsUserAuthenticated = (object)m_windowsPrincipal != null && m_windowsPrincipal.Identity.IsAuthenticated; } return IsUserAuthenticated; } /// <summary> /// Refreshes the <see cref="UserData"/> from the backend data store. /// </summary> /// <returns>true if <see cref="SecurityProviderBase.UserData"/> is refreshed, otherwise false.</returns> public override bool RefreshData() { // For consistency with WindowIdentity principal, user groups are loaded into Roles collection UserData userData = new UserData(UserData.Username); bool result = RefreshData(userData, userData.Roles, ProviderID); if (result) { string[] parts; // Remove domain name prefixes from user group names (again to match WindowIdentity principal implementation) for (int i = 0; i < userData.Roles.Count; i++) { parts = userData.Roles[i].Split('\\'); if (parts.Length == 2) userData.Roles[i] = parts[1]; } } UserData = userData; return result; } /// <summary> /// Refreshes the <see cref="UserData"/> from the backend data store loading user groups into desired collection. /// </summary> /// <param name="userData">The structure for the data being refreshed.</param> /// <param name="groupCollection">Target collection for user groups.</param> /// <param name="providerID">Unique provider ID used to distinguish cached user data that may be different based on provider.</param> /// <returns>true if <see cref="SecurityProviderBase.UserData"/> is refreshed, otherwise false.</returns> protected virtual bool RefreshData(UserData userData, List<string> groupCollection, int providerID) { if ((object)groupCollection == null) throw new ArgumentNullException(nameof(groupCollection)); if (string.IsNullOrEmpty(userData.Username)) return false; // Initialize user data userData.Initialize(); // Populate user data UserInfo user = null; UserDataCache userDataCache = null; try { // Get current local user data cache if (m_enableOfflineCaching) { userDataCache = UserDataCache.GetCurrentCache(providerID); userDataCache.RetryDelayInterval = m_cacheRetryDelayInterval; userDataCache.MaximumRetryAttempts = m_cacheMaximumRetryAttempts; userDataCache.ReloadOnChange = false; userDataCache.AutoSave = true; userDataCache.Load(); } // Create user info object using specified LDAP path if provided string ldapPath = GetLdapPath(); if (string.IsNullOrEmpty(ldapPath)) user = new UserInfo(userData.Username); else user = new UserInfo(userData.Username, ldapPath); // Make sure to load privileged user credentials from config file if present. user.PersistSettings = true; // If the system is currently logged in under a domain account and the system is disconnected from the domain, // UserInfo will need access to the passthrough principal to determine whether the user exists on the domain user.PassthroughPrincipal = PassthroughPrincipal; // Attempt to determine if user exists (this will initialize user object if not initialized already) userData.IsDefined = user.Exists; userData.LoginID = user.LoginID; // Fill in user information from domain data if it is available if (!user.DomainRespondsForUser) { // Attempt to load previously cached user information when domain is offline UserData cachedUserData; if ((object)userDataCache != null && userDataCache.TryGetUserData(userData.LoginID, out cachedUserData)) { // Copy relevant cached user information userData.IsDefined = true; userData.FirstName = cachedUserData.FirstName; userData.LastName = cachedUserData.LastName; userData.CompanyName = cachedUserData.CompanyName; userData.PhoneNumber = cachedUserData.PhoneNumber; userData.EmailAddress = cachedUserData.EmailAddress; userData.IsLockedOut = cachedUserData.IsLockedOut; userData.IsDisabled = cachedUserData.IsDisabled; userData.Roles.AddRange(cachedUserData.Roles); userData.Groups.AddRange(cachedUserData.Groups); // If domain is offline, a password change cannot be initiated userData.PasswordChangeDateTime = DateTime.MaxValue; userData.AccountCreatedDateTime = cachedUserData.AccountCreatedDateTime; } else { // No previous user data was cached and domain is currently unavailable, however Windows could // allow authentication using cached credentials when authenticating, so all we can do at this // point is assume that user "could" exist and allow authentication to be attempted userData.IsDefined = true; userData.IsLockedOut = false; userData.IsDisabled = false; userData.PasswordChangeDateTime = DateTime.MaxValue; userData.AccountCreatedDateTime = DateTime.MinValue; } } else if (userData.IsDefined) { // Copy relevant user information userData.FirstName = user.FirstName; userData.LastName = user.LastName; userData.CompanyName = user.Company; userData.PhoneNumber = user.Telephone; userData.EmailAddress = user.Email; try { userData.IsLockedOut = user.AccountIsLockedOut; userData.IsDisabled = user.AccountIsDisabled; } catch (SecurityException) { // AD may restrict information on account availability, if so, have to make a safe assumption: userData.IsLockedOut = true; userData.IsDisabled = true; } userData.PasswordChangeDateTime = user.NextPasswordChangeDate; userData.AccountCreatedDateTime = user.AccountCreationDate; // Assign all groups the user is a member of foreach (string groupName in user.Groups) { if (!groupCollection.Contains(groupName, StringComparer.OrdinalIgnoreCase)) groupCollection.Add(groupName); } if ((object)userDataCache != null) { // Cache user data so that information can be loaded later if domain is unavailable userDataCache[userData.LoginID] = userData; // Wait for pending serialization since cache is scoped locally to this method and will be disposed before exit userDataCache.WaitForSave(); } } return userData.IsDefined; } finally { if ((object)user != null) { user.PersistSettings = false; user.Dispose(); } if ((object)userDataCache != null) userDataCache.Dispose(); } } /// <summary> /// Changes user password in the backend data store.