//****************************************************************************************************** // SecurityProviderCache.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: // ---------------------------------------------------------------------------------------------------- // 06/25/2010 - Pinal C. Patel // Generated original version of source code. // 01/27/2011 - Pinal C. Patel // Updated SetupPrincipal() to call AppDomain.SetThreadPrincipal() to set the thread principal // in addition to settings the current thread principal so the thread principal is set correctly // inside WPF applications. // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // 04/27/2016 - J. Ritchie Carroll // Added ValidateCurrentProvider method to simplify CurrentProvider usage patten; code clean-up. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Timers; using GSF.Collections; using GSF.Configuration; using GSF.Threading; using Timer = System.Timers.Timer; namespace GSF.Security { /// /// A helper class that manages the caching of s. /// public static class SecurityProviderCache { #region [ Members ] // Nested Types /// /// A class that facilitates the caching of . /// private class CacheContext { #region [ Members ] // Fields private readonly ISecurityProvider m_provider; private readonly WeakReference m_weakProvider; private DateTime m_lastRefreshTime; private DateTime m_lastAccessTime; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// public CacheContext(ISecurityProvider provider) : this(provider, null) { } /// /// Initializes a new instance of the class. /// public CacheContext(WeakReference weakProvider) : this(null, weakProvider) { } private CacheContext(ISecurityProvider provider, WeakReference weakProvider) { DateTime now = DateTime.UtcNow; m_provider = provider; m_weakProvider = weakProvider; m_lastRefreshTime = now; m_lastAccessTime = now; } #endregion #region [ Properties ] /// /// Gets the managed by this . /// public ISecurityProvider Provider { get { ISecurityProvider provider; m_lastAccessTime = DateTime.UtcNow; if ((object)m_provider != null) return m_provider; if ((object)m_weakProvider != null && m_weakProvider.TryGetTarget(out provider)) return provider; return null; } } /// /// Gets the of when the was last refreshed. /// public DateTime LastRefreshTime => m_lastRefreshTime; /// /// Gets the of when the was last accessed. /// public DateTime LastAccessTime => m_lastAccessTime; #endregion #region [ Methods ] /// /// Refreshes the provider managed by this . /// public bool Refresh() { ISecurityProvider provider = Provider; if ((object)provider == null) return false; provider.RefreshData(); provider.Authenticate(); m_lastRefreshTime = DateTime.UtcNow; return true; } #endregion } // Constants /// /// Number of minutes up to which s are to be cached. /// private const int DefaultUserCacheTimeout = 5; /// /// Number of milliseconds between calls to monitor the cache. /// private const int CacheMonitorTimerInterval = 60000; #endregion #region [ Static ] // Static Fields private static readonly Dictionary s_cache; private static readonly List s_autoRefreshProviders; private static readonly int s_userCacheTimeout; private static readonly Action s_cacheMonitorAction; // Static Constructor static SecurityProviderCache() { // Load settings from the specified category ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[SecurityProviderBase.DefaultSettingsCategory]; settings.Add("UserCacheTimeout", DefaultUserCacheTimeout, "Defines the timeout, in whole minutes, for a user's provider cache. Any value less than 1 will cause cache reset every minute."); s_userCacheTimeout = settings["UserCacheTimeout"].ValueAs(DefaultUserCacheTimeout); // Initialize static variables s_cache = new Dictionary(); s_autoRefreshProviders = new List(); s_cacheMonitorAction = new Action(ManageCachedCredentials); s_cacheMonitorAction.DelayAndExecute(CacheMonitorTimerInterval); } // Static Methods /// /// Creates a new provider from data cached by the . /// /// The username of the user for which to create a new provider. /// obtained through alternative authentication mechanisms to provide authentication for the . /// Indicates whether the provider should be automatically refreshed on a timer. /// A new provider initialized from cached data. public static ISecurityProvider CreateProvider(string username, IPrincipal passthroughPrincipal = null, bool autoRefresh = true) { CacheContext cacheContext; lock (s_cache) { cacheContext = s_cache.GetOrAdd(username, name => new CacheContext(SecurityProviderUtility.CreateProvider(username, passthroughPrincipal))); } ISecurityProvider provider = SecurityProviderUtility.CreateProvider(cacheContext.Provider.UserData); provider.PassthroughPrincipal = passthroughPrincipal; if (autoRefresh) AutoRefresh(provider); return provider; } /// /// Removes any cached information about the user with the given username. /// /// The username of the user to be flushed from the cache. public static void Flush(string username) { lock (s_cache) { s_cache.Remove(username); } } /// /// Adds the given provider to the collection of providers being automatically refreshed on the user cache timeout interval. /// /// The security provider to be cached. public static void AutoRefresh(ISecurityProvider provider) { lock (s_autoRefreshProviders) { WeakReference weakProvider = new WeakReference(provider); CacheContext cacheContext = new CacheContext(weakProvider); s_autoRefreshProviders.Add(cacheContext); } } /// /// Removes the given provider from the collection of providers being automatically refreshed. /// /// The provider to be removed. public static void DisableAutoRefresh(ISecurityProvider provider) { lock (s_autoRefreshProviders) { s_autoRefreshProviders.RemoveAll(cacheContext => provider.Equals(cacheContext.Provider)); } } /// /// Forces all cached providers to refresh state. /// public static void RefreshAll() { lock (s_cache) { s_cache.Clear(); } List refreshedContexts = new List(); lock (s_autoRefreshProviders) { for (int i = s_autoRefreshProviders.Count - 1; i >= 0; i--) { CacheContext cacheContext = s_autoRefreshProviders[i]; if ((object)cacheContext.Provider == null) { s_autoRefreshProviders[i] = s_autoRefreshProviders[s_autoRefreshProviders.Count - 1]; s_autoRefreshProviders.RemoveAt(s_autoRefreshProviders.Count - 1); } else { refreshedContexts.Add(cacheContext); } } } // It's important to avoid calling refresh // inside the lock for performance reasons foreach (CacheContext cacheContext in refreshedContexts) cacheContext.Refresh(); } private static void ManageCachedCredentials() { HashSet refreshedContexts = new HashSet(); lock (s_cache) { foreach (string username in s_cache.Keys.ToList()) { CacheContext cacheContext = s_cache[username]; if (DateTime.UtcNow.Subtract(cacheContext.LastAccessTime).TotalMinutes > s_userCacheTimeout) s_cache.Remove(username); else refreshedContexts.Add(s_cache[username]); } } lock (s_autoRefreshProviders) { for (int i = s_autoRefreshProviders.Count - 1; i >= 0; i--) { CacheContext cacheContext = s_autoRefreshProviders[i]; if (DateTime.UtcNow.Subtract(cacheContext.LastRefreshTime).TotalMinutes > s_userCacheTimeout) { if ((object)cacheContext.Provider == null) { s_autoRefreshProviders[i] = s_autoRefreshProviders[s_autoRefreshProviders.Count - 1]; s_autoRefreshProviders.RemoveAt(s_autoRefreshProviders.Count - 1); } else { refreshedContexts.Add(cacheContext); } } } } // It's important to avoid calling refresh // inside the lock for performance reasons foreach (CacheContext cacheContext in refreshedContexts) cacheContext.Refresh(); // The refresh could take several minutes so we should // wait to kick off the timer until after we are finished s_cacheMonitorAction.DelayAndExecute(CacheMonitorTimerInterval); } #endregion } }