//******************************************************************************************************
// 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
///
/// Represents an HTTP module that can be used to enable site-wide role-based security.
///
///
/// Required config file entries:
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
///
public class SecurityModule : IHttpModule
{
#region [ Members ]
// Nested Types
///
/// A handler used to force the SessionStateModule to load session state.
///
private class SessionEnabledHandler : IHttpHandler, IRequiresSessionState
{
public IHttpHandler OriginalHandler;
///
/// Initializes a new instance of the class.
///
/// The original handler object.
public SessionEnabledHandler(IHttpHandler originalHandler)
{
OriginalHandler = originalHandler;
}
///
/// This method will never get called.
///
public void ProcessRequest(HttpContext context)
{
throw new NotSupportedException();
}
///
/// Returns false since class has a member.
///
public bool IsReusable
{
get { return false; }
}
}
// Fields
private HttpApplication m_application;
#endregion
#region [ Properties ]
///
/// Gets the current instance.
///
protected HttpApplication Application
{
get
{
return m_application;
}
}
#endregion
#region [ Methods ]
///
/// Initializes the .
///
/// An object.
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());
}
///
/// Releases the resources used by .
///
public void Dispose()
{
m_application.PostMapRequestHandler -= Application_PostMapRequestHandler;
m_application.PostAcquireRequestState -= Application_PostAcquireRequestState;
m_application.PreRequestHandlerExecute -= Application_PreRequestHandlerExecute;
}
///
/// Redirects the client browser based on the specified
///
/// to be used for the redirect.
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);
}
///
/// Gets the name of resource being accessed.
///
/// .. property value.
protected virtual string GetResourceName()
{
return m_application.Request.Url.PathAndQuery;
}
///
/// Determines if access to the requested resource is restricted by .
///
/// true if access to the requested resource is restricted, otherwise false.
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
}
}