265 lines · 11.1 KB · C#
RawDownload
//******************************************************************************************************
//  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 Eclipse Public License -v 1.0 (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/eclipse-1.0.php
//
//  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.
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.Security.Principal;
using System.Threading;
using System.Timers;
using System.Web;
using Timer = System.Timers.Timer;

namespace GSF.Security
{
    /// <summary>
    /// A helper class that manages the caching of <see cref="ISecurityProvider"/>s.
    /// </summary>
    public static class SecurityProviderCache
    {
        #region [ Members ]

        // Nested Types

        /// <summary>
        /// A class that facilitates the caching of <see cref="ISecurityProvider"/>.
        /// </summary>
        private class CacheContext
        {
            private readonly ISecurityProvider m_provider;
            private DateTime m_lastAccessed;

            /// <summary>
            /// Initializes a new instance of the <see cref="CacheContext"/> class.
            /// </summary>
            public CacheContext(ISecurityProvider provider)
            {
                m_provider = provider;
                m_lastAccessed = DateTime.Now;
            }

            /// <summary>
            /// Gets the <see cref="ISecurityProvider"/> managed by this <see cref="CacheContext"/>.
            /// </summary>
            public ISecurityProvider Provider
            {
                get
                {
                    m_lastAccessed = DateTime.Now;
                    return m_provider;
                }
            }

            /// <summary>
            /// Gets the <see cref="DateTime"/> of when the <see cref="Provider"/> was last accessed.
            /// </summary>
            public DateTime LastAccessed
            {
                get
                {
                    return m_lastAccessed;
                }
            }
        }

        // Constants

        /// <summary>
        /// Number of minutes upto which <see cref="ISecurityProvider"/>s are to be cached.
        /// </summary>
        private const int CachingTimeout = 20;

        #endregion

        #region [ Static ]

        // Static Fields
        private static bool s_threadPolicySet;
        private static readonly IDictionary<string, CacheContext> s_cache;
        private static readonly Timer s_cacheMonitorTimer;

        // Static Constructor
        static SecurityProviderCache()
        {
            // Initialize static variables.
            s_cache = new Dictionary<string, CacheContext>(StringComparer.CurrentCultureIgnoreCase);
            s_cacheMonitorTimer = new Timer(60000);
            s_cacheMonitorTimer.Elapsed += CacheMonitorTimer_Elapsed;
            s_cacheMonitorTimer.Start();
        }

        // Static Properties

        /// <summary>
        /// Gets or sets the <see cref="ISecurityProvider"/> of the current user.
        /// </summary>
        public static ISecurityProvider CurrentProvider
        {
            get
            {
                // Logic behind caching of the provider:
                // - A provider is cached to session state data if the runtime is ASP.NET and if the session state 
                //   data is accessible. This would essentially mean that we're dealing with web sites or web services
                //   that are either SOAP ASMX services or WCF services hosted in ASP.NET compatibility mode.
                // - A provider is cached to in-process static memory if we don't have access to session state data. 
                //   This would essentially mean that we're either dealing with windows based application or WCF 
                //   service hosted inside ASP.NET runtime without compatibility mode enabled.
                SecurityPrincipal principal = Thread.CurrentPrincipal as SecurityPrincipal;
                if (principal != null)
                {
                    // The provider we're looking for is available to us via the current thread principal. This means
                    // that the current thread principal has already been set by a call to Current property setter.
                    return ((SecurityIdentity)principal.Identity).Provider;
                }
                else
                {
                    // Since the provider is not available to us through the current thread principal, we check to see 
                    // if it is available to us via one of the two caching mechanisms.
                    if (HttpContext.Current != null && HttpContext.Current.Session != null)
                    {
                        // Check session state.
                        ISecurityProvider provider = HttpContext.Current.Session[typeof(ISecurityProvider).Name] as ISecurityProvider;
                        if (provider == null)
                            return null;
                        else
                            return SetupPrincipal(provider, false);
                    }
                    else
                    {
                        // Check in-process memory.
                        CacheContext cache;
                        lock (s_cache)
                        {
                            s_cache.TryGetValue(Thread.CurrentPrincipal.Identity.Name, out cache);
                        }

                        if (cache == null)
                            return null;
                        else
                            return SetupPrincipal(cache.Provider, false);
                    }
                }
            }
            set
            {
                if (value != null)
                {
                    // Login - Setup security principal.
                    value.Initialize();
                    SetupPrincipal(value, false);
                    if (HttpContext.Current != null && HttpContext.Current.Session != null)
                        // Cache provider to session state.
                        HttpContext.Current.Session[typeof(ISecurityProvider).Name] = value;
                    else if (!string.IsNullOrEmpty(value.UserData.LoginID))
                        // Cache provider to in-process memory.
                        lock (s_cache)
                        {
                            s_cache[value.UserData.LoginID] = new CacheContext(value);
                        }
                }
                else
                {
                    // Logout - Restore original principal.
                    SecurityPrincipal principal = Thread.CurrentPrincipal as SecurityPrincipal;
                    if (principal == null)
                        return;

                    SecurityIdentity identity = (SecurityIdentity)principal.Identity;
                    SetupPrincipal(identity.Provider, true);

                    if (HttpContext.Current != null && HttpContext.Current.Session != null)
                        // Remove previously cached provider from session state.
                        HttpContext.Current.Session[typeof(ISecurityProvider).Name] = null;
                    else if (s_cache.ContainsKey(identity.Provider.UserData.LoginID))
                        // Remove previously cached provider from in-process memory.
                        lock (s_cache)
                        {
                            s_cache.Remove(identity.Provider.UserData.LoginID);
                        }
                }
            }
        }

        // Static Methods

        /// <summary>
        /// Attempts to get cached <see cref="ISecurityProvider"/> for the given username.
        /// </summary>
        /// <param name="username">Name of the user.</param>
        /// <param name="provider">Security provider to return.</param>
        /// <returns>True if provider is cached; false otherwise.</returns>
        public static bool TryGetCachedProvider(string username, out ISecurityProvider provider)
        {
            CacheContext cacheContext;
            bool result = s_cache.TryGetValue(username, out cacheContext);
            provider = result ? cacheContext.Provider : null;
            return result;
        }

        private static ISecurityProvider SetupPrincipal(ISecurityProvider provider, bool restore)
        {
            // Initialize the principal object.
            IPrincipal principal;
            if (restore)
                // Set principal to anonymous WindowsPrincipal.
                principal = new WindowsPrincipal(WindowsIdentity.GetAnonymous());
            else
                // Set principal to SecurityPrincipal.
                principal = new SecurityPrincipal(new SecurityIdentity(provider));

            // Setup the current thread principal.
            Thread.CurrentPrincipal = principal;
            if (!s_threadPolicySet)
            {
                AppDomain.CurrentDomain.SetThreadPrincipal(Thread.CurrentPrincipal);
                s_threadPolicySet = true;
            }

            // Setup ASP.NET remote user principal.
            if (HttpContext.Current != null)
                HttpContext.Current.User = Thread.CurrentPrincipal;

            return provider;
        }

        private static void CacheMonitorTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            lock (s_cache)
            {
                List<string> cacheKeys = new List<string>(s_cache.Keys);
                foreach (string cacheKey in cacheKeys)
                {
                    if (DateTime.Now.Subtract(s_cache[cacheKey].LastAccessed).TotalMinutes > CachingTimeout)
                        s_cache.Remove(cacheKey);
                }
            }
        }

        #endregion
    }
}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t