305 lines · 13.6 KB · C#
RawDownload
//******************************************************************************************************
//  SecurityModule.cs - Gbtc
//
//  Copyright © 2010, 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:
//  ----------------------------------------------------------------------------------------------------
//  03/31/2010 - Pinal C. Patel
//       Generated original version of source code.
//  05/27/2010 - Pinal C. Patel
//       Added usage example to code comments.
//  06/30/2010 - Pinal C. Patel
//       Modified redirection logic to support security of static resources (*.txt, *.pdf, *.exe).
//  07/01/2010 - Pinal C. Patel
//       Modified redirection logic to allow for custom redirection using customErrors settings.
//  08/11/2010 - Pinal C. Patel
//       Made key methods virtual for extensibility.
//  10/14/2010 - Pinal C. Patel
//       Modified GetResourceName() to return path and query for consistency with SecurityPolicy.
//  01/06/2011 - Pinal C. Patel
//       Implemented logic to use RestrictAccessAttribute for access control on the requested resource.
//  06/09/2011 - Pinal C. Patel
//       Added null reference check in IsAccessRestricted() method.
//  07/29/2011 - Pinal C. Patel
//       Made the referral URL passed to SecurityPortal.aspx page relative so redirection work correctly
//       when reverse proxy is involved where requested URL that the web server sees is different than 
//       what the user requested.
//  08/18/2011 - Pinal C. Patel
//       Made Redirect() overridable by deriving classes to control the redirection behavior.
//  08/19/2011 - Pinal C. Patel
//       Changed the type input parameter for Redirect() from int to HttpStatusCode.
//  10/8/2012 - Danyelle Gilliam
//        Modified Header
//
//******************************************************************************************************

using GSF.Configuration;
using GSF.Security;
using GSF.Web.Hosting;
using System;
using System.Net;
using System.Web;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Web.SessionState;

namespace GSF.Web
{
    #region [ Enumerations ]

    #endregion

    /// <summary>
    /// Represents an <see cref="IHttpModule">HTTP module</see> that can be used to enable site-wide role-based security.
    /// </summary>
    /// <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="" description="Connection string to be used for connection to the backend security datastore."
    ///         encrypted="false" />
    ///       <add name="ProviderType" value="GSF.Security.LdapSecurityProvider, GSF.Security"
    ///         description="The type to be used for enforcing security." 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="*/WebResource.axd*;*/SecurityPortal.aspx*"
    ///         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" />
    ///     </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>
    ///   <system.web>
    ///     <authentication mode="Windows"/>
    ///     <httpModules>
    ///       <add name="SecurityModule" type="GSF.Web.SecurityModule, GSF.Web" />
    ///     </httpModules>
    ///   </system.web>
    /// </configuration>
    /// ]]>
    /// </code>
    /// </example>
    /// <seealso cref="ISecurityProvider"/>
    public class SecurityModule : IHttpModule
    {
        #region [ Members ]

        // Nested Types

        /// <summary>
        /// A handler used to force the SessionStateModule to load session state.
        /// </summary>
        private class SessionEnabledHandler : IHttpHandler, IRequiresSessionState
        {
            public IHttpHandler OriginalHandler;

            /// <summary>
            /// Initializes a new instance of the <see cref="SessionEnabledHandler"/> class.
            /// </summary>
            /// <param name="originalHandler">The original handler object.</param>
            public SessionEnabledHandler(IHttpHandler originalHandler)
            {
                OriginalHandler = originalHandler;
            }

            /// <summary>
            /// This method will never get called.
            /// </summary>
            public void ProcessRequest(HttpContext context)
            {
                throw new NotSupportedException();
            }

            /// <summary>
            /// Returns false since class has a member.
            /// </summary>
            public bool IsReusable
            {
                get { return false; }
            }
        }

        // Fields
        private HttpApplication m_application;

        #endregion

        #region [ Properties ]

        /// <summary>
        /// Gets the current <see cref="HttpApplication"/> instance.
        /// </summary>
        protected HttpApplication Application
        {
            get 
            {
                return m_application;
            }
        }

        #endregion

        #region [ Methods ]

        /// <summary>
        /// Initializes the <see cref="SecurityModule"/>.
        /// </summary>
        /// <param name="context">An <see cref="HttpApplication"/> object.</param>
        public void Init(HttpApplication context)
        {
            m_application = context;
            m_application.PostMapRequestHandler += Application_PostMapRequestHandler;
            m_application.PostAcquireRequestState += Application_PostAcquireRequestState;
            m_application.PreRequestHandlerExecute += Application_PreRequestHandlerExecute;

            if (!(HostingEnvironment.VirtualPathProvider is EmbeddedResourcePathProvider))
                HostingEnvironment.RegisterVirtualPathProvider(new EmbeddedResourcePathProvider());
        }

        /// <summary>
        /// Releases the resources used by <see cref="SecurityModule"/>.
        /// </summary>
        public void Dispose()
        {
            m_application.PostMapRequestHandler -= Application_PostMapRequestHandler;
            m_application.PostAcquireRequestState -= Application_PostAcquireRequestState;
            m_application.PreRequestHandlerExecute -= Application_PreRequestHandlerExecute;
        }

        /// <summary>
        /// Redirects the client browser based on the specified <paramref name="httpStatusCode"/>
        /// </summary>
        /// <param name="httpStatusCode"><see cref="HttpStatusCode"/> to be used for the redirect.</param>
        protected virtual void Redirect(HttpStatusCode httpStatusCode)
        {
            int statusCode = (int)httpStatusCode;
            if (m_application.Context.IsCustomErrorEnabled)
            {
                // Defer redirect to customErrors settings in the config file to allow for custom error pages.
                ConfigurationFile configFile = ConfigurationFile.Current;
                CustomErrorsSection customErrors = configFile.Configuration.GetSection("system.web/customErrors") as CustomErrorsSection;
                if (customErrors != null && customErrors.Errors[statusCode.ToString()] != null)
                {
                    // Set status code for the response.
                    m_application.Context.Response.StatusCode = statusCode;
                    // Throw exception for ASP.NET pipeline to takeover processing.
                    throw new HttpException(statusCode, string.Format("Security exception (HTTP status code: {0})", statusCode));
                }
            }

            // Abruptly ending the processing caused by a redirect does not work well when processing static content.
            string redirectUrl = string.Format("~/SecurityPortal.aspx?s={0}&r={1}", statusCode, HttpUtility.UrlEncode(VirtualPathUtility.ToAppRelative(m_application.Request.Url.PathAndQuery)));
            if (m_application.Context.Handler is DefaultHttpHandler)
                // Accessed resource is static.
                m_application.Context.Response.Redirect(redirectUrl, false);
            else
                // Accessed resource is dynamic.
                m_application.Context.Response.Redirect(redirectUrl, true);
        }

        /// <summary>
        /// Gets the name of resource being accessed.
        /// </summary>
        /// <returns><see cref="HttpApplication.Request"/>.<see cref="HttpRequest.Url"/>.<see cref="Uri.PathAndQuery"/> property value.</returns>
        protected virtual string GetResourceName()
        {
            return m_application.Request.Url.PathAndQuery;
        }

        /// <summary>
        /// Determines if access to the requested resource is restricted by <see cref="RestrictAccessAttribute"/>.
        /// </summary>
        /// <returns>true if access to the requested resource is restricted, otherwise false.</returns>
        protected virtual bool IsAccessRestricted()
        {
            // Check for the request handler.
            IHttpHandler handler = m_application.Context.Handler;
            if (handler == null)
                return false;

            // Evaluate access restriction if defined.
            object[] attributes = handler.GetType().GetCustomAttributes(typeof(RestrictAccessAttribute), true);
            if (attributes.Length > 0)
                return !((RestrictAccessAttribute)attributes[0]).CheckAccess();
            else
                return false;
        }

        private void Application_PostMapRequestHandler(object sender, EventArgs e)
        {
            if (!SecurityProviderUtility.IsResourceSecurable(GetResourceName()))
                return;

            if (m_application.Context.Handler is IReadOnlySessionState ||
                m_application.Context.Handler is IRequiresSessionState)
                // no need to replace the current handler 
                return;

            // swap the current handler 
            m_application.Context.Handler = new SessionEnabledHandler(m_application.Context.Handler);
        }

        private void Application_PostAcquireRequestState(object sender, EventArgs e)
        {
            if (!SecurityProviderUtility.IsResourceSecurable(GetResourceName()))
                return;

            SessionEnabledHandler handler = HttpContext.Current.Handler as SessionEnabledHandler;
            if (handler != null)
                // set the original handler back 
                HttpContext.Current.Handler = handler.OriginalHandler;
        }

        private void Application_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            string resource = GetResourceName();
            if (!SecurityProviderUtility.IsResourceSecurable(resource))
                return;

            if (SecurityProviderCache.CurrentProvider == null)
                SecurityProviderCache.CurrentProvider = SecurityProviderUtility.CreateProvider(string.Empty);

            if (!m_application.User.Identity.IsAuthenticated)
                // Failed to authenticate user.
                Redirect(HttpStatusCode.Unauthorized);

            if (IsAccessRestricted() || 
                !SecurityProviderUtility.IsResourceAccessible(resource))
                // User does not have access to the resource.
                Redirect(HttpStatusCode.Forbidden);
        }

        #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