//******************************************************************************************************
// 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 GSF.Collections;
using GSF.Configuration;
using GSF.Threading;
namespace GSF.Security
{
///
/// A helper class that manages the caching of s.
///
public 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 bool m_disposed;
#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;
LastRefreshTime = now;
LastAccessTime = now;
}
#endregion
#region [ Properties ]
///
/// Gets the managed by this .
///
public ISecurityProvider Provider
{
get
{
LastAccessTime = DateTime.UtcNow;
return _Provider;
}
}
///
/// Gets the of when the was last refreshed.
///
public DateTime LastRefreshTime { get; private set; }
///
/// Gets the of when the was last accessed.
///
public DateTime LastAccessTime { get; private set; }
// Gets the provider without updating LastAccessTime.
private ISecurityProvider _Provider
{
get
{
if (m_disposed)
return null;
if ((object)m_provider != null)
return m_provider;
if ((object)m_weakProvider != null && m_weakProvider.TryGetTarget(out ISecurityProvider provider))
return provider;
return null;
}
}
#endregion
#region [ Methods ]
///
/// Refreshes the provider managed by this .
///
public bool Refresh()
{
try
{
ISecurityProvider provider = _Provider;
if ((object)provider == null)
return false;
if (provider.CanRefreshData)
provider.RefreshData();
provider.Authenticate();
LastRefreshTime = DateTime.UtcNow;
return true;
}
catch (ObjectDisposedException)
{
m_disposed = true;
return false;
}
}
#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;
// Fields
private readonly Dictionary s_cache;
private readonly List s_autoRefreshProviders;
private readonly int s_userCacheTimeout;
private readonly Action s_cacheMonitorAction;
private string m_settingsCategory;
#endregion
#region [ Constructor ]
///
/// Creates a new .
///
/// Settings category.
public SecurityProviderCache(string settingsCategory)
{
// Load settings from the specified category
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[settingsCategory];
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);
m_settingsCategory = settingsCategory;
}
#endregion
#region [ 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 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, m_settingsCategory)));
}
ISecurityProvider provider = SecurityProviderUtility.CreateProvider(cacheContext.Provider.UserData, m_settingsCategory);
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 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 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 void DisableAutoRefresh(ISecurityProvider provider)
{
lock (s_autoRefreshProviders)
{
s_autoRefreshProviders.RemoveAll(cacheContext => provider.Equals(cacheContext.Provider));
}
}
///
/// Forces all cached providers to refresh state.
///
public 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 void ManageCachedCredentials()
{
DateTime now = DateTime.UtcNow;
void RefreshCache()
{
var cachedProviders = Enumerable.Empty