//******************************************************************************************************
// AuthenticationOptions.cs - Gbtc
//
// Copyright © 2017, 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://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:
// ----------------------------------------------------------------------------------------------------
// 08/25/2017 - Stephen C. sWills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Text.RegularExpressions;
namespace GSF.Web.Security
{
///
/// Represents options for authentication using .
///
public sealed class AuthenticationOptions : Microsoft.Owin.Security.AuthenticationOptions
{
#region [ Members ]
// Constants
///
/// Default value for .
///
public const string DefaultRequestVerificationToken = "X-GSF-Verify";
///
/// Default value for .
///
public const string DefaultAuthFailureRedirectResourceExpression = @"^/$|^/.+\.cshtml$|^/.+\.vbhtml$";
///
/// Default value for .
///
public const string DefaultAnonymousResourceExpression = "^/@|^/favicon.ico$";
///
/// Default value for .
///
public const string DefaultLoginPage = "/@GSF/Web/Security/Views/Login.cshtml";
///
/// Default value for .
///
public const string DefaultAuthTestPage = "/AuthTest";
///
/// Default value for .
///
public const AuthenticationSchemes DefaultAuthenticationSchemes = AuthenticationSchemes.Ntlm | AuthenticationSchemes.Basic;
///
/// Default value for .
///
public const string DefaultClearCredentialsParameter = "clearcredentials";
// Fields
private readonly ConcurrentDictionary m_authFailureRedirectResourceCache;
private readonly ConcurrentDictionary m_anonymousResourceCache;
private string m_authFailureRedirectResourceExpression;
private string m_anonymousResourceExpression;
private Regex m_authFailureRedirectResources;
private Regex m_anonymousResources;
private string m_realm;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class.
///
public AuthenticationOptions() : base(SessionHandler.DefaultAuthenticationToken)
{
m_authFailureRedirectResourceCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
m_anonymousResourceCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
}
#endregion
#region [ Properties ]
///
/// Gets or sets the expression that will match paths for the resources on the web server
/// that should redirect to the when authentication fails.
///
public string AuthFailureRedirectResourceExpression
{
get
{
return m_authFailureRedirectResourceExpression;
}
set
{
m_authFailureRedirectResourceExpression = value;
m_authFailureRedirectResources = new Regex(m_authFailureRedirectResourceExpression, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
}
}
///
/// Gets or sets the expression that will match paths for the resources on the web server
/// that can be provided without checking credentials.
///
public string AnonymousResourceExpression
{
get
{
return m_anonymousResourceExpression;
}
set
{
m_anonymousResourceExpression = value;
m_anonymousResources = new Regex(m_anonymousResourceExpression, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
}
}
///
/// Gets or sets the token used for identifying the authentication token in cookie headers.
///
public string AuthenticationToken { get; set; } = SessionHandler.DefaultAuthenticationToken;
///
/// Gets or sets the token used for identifying the session ID in cookie headers.
///
public string SessionToken { get; set; } = SessionHandler.DefaultSessionToken;
///
/// Gets or sets the token used for anti-forgery verification in HTTP request headers.
///
public string RequestVerificationToken { get; set; } = DefaultRequestVerificationToken;
///
/// Gets or sets the login page used as a redirect location when authentication fails.
///
public string LoginPage { get; set; } = DefaultLoginPage;
///
/// Gets or sets the page name used to test user authorization.
///
public string AuthTestPage { get; set; } = DefaultAuthTestPage;
///
/// Gets or sets the authentication schemes to use when testing authentication with the .
///
public AuthenticationSchemes AuthenticationSchemes { get; set; } = DefaultAuthenticationSchemes;
///
/// Gets or sets the parameter name for the that forces it to use Basic authentication
/// so that any cached browser credentials can be cleared.
///
public string ClearCredentialsParameter { get; set; } = DefaultClearCredentialsParameter;
///
/// Gets or sets the case-sensitive identifier that defines the protection space for this authentication.
///
///
///
/// The "realm" authentication parameter is reserved for use by authentication schemes that wish to
/// indicate a scope of protection.
///
///
/// A protection space is defined by the canonical root URI (the scheme and authority components of the
/// effective request URI) of the server being accessed, in combination with the realm value if present.
/// These realms allow the protected resources on a server to be partitioned into a set of protection
/// spaces, each with its own authentication scheme and/or authorization database. The realm value is a
/// string, generally assigned by the origin server, that can have additional semantics specific to the
/// authentication scheme. Note that a response can have multiple challenges with the same auth-scheme
/// but with different realms.
///
///
public string Realm
{
get
{
return m_realm;
}
set
{
if (string.IsNullOrWhiteSpace(value))
{
m_realm = null;
return;
}
// Verify that Realm does not contain a quote character unless properly
// escaped, i.e., preceded by a backslash that is not itself escaped
if (value.Length != Regex.Replace(value, @"\\\\""|(?
/// Gets or sets any custom header to be displayed on the .
///
public string LoginHeader { get; set; }
///
/// Gets an immutable version of the authentication options.
///
public ReadonlyAuthenticationOptions Readonly => new ReadonlyAuthenticationOptions(this);
#endregion
#region [ Methods ]
///
/// Determines whether the given resource is an authentication failure redirect resource.
///
/// Path to check as an anonymous resource.
/// true if path is an anonymous resource; otherwise, false.
public bool IsAuthFailureRedirectResource(string urlPath)
{
if ((object)m_authFailureRedirectResourceExpression == null)
AuthFailureRedirectResourceExpression = DefaultAuthFailureRedirectResourceExpression;
return m_authFailureRedirectResourceCache.GetOrAdd(urlPath, m_authFailureRedirectResources.IsMatch);
}
///
/// Determines whether the given resource is an anonymous resource.
///
/// Path to check as an anonymous resource.
/// true if path is an anonymous resource; otherwise, false.
public bool IsAnonymousResource(string urlPath)
{
if ((object)m_anonymousResourceExpression == null)
AnonymousResourceExpression = DefaultAnonymousResourceExpression;
return m_anonymousResourceCache.GetOrAdd(urlPath, path =>
{
string resource = path;
bool state;
// If path is an embedded resource, convert it to a properly formatted type name identifier
if (resource.StartsWith("/@"))
resource = resource.Substring(2).Replace('/', '.');
else if (resource.StartsWith("@"))
resource = resource.Substring(1).Replace('/', '.');
if (s_resourceRequiresAuthentication.TryGetValue(resource, out state))
return !state;
// Always match against unmodified original path
return m_anonymousResources.IsMatch(path);
});
}
#endregion
#region [ Static ]
// Static Fields
private static readonly ConcurrentDictionary s_resourceRequiresAuthentication;
// Static Constructor
static AuthenticationOptions()
{
s_resourceRequiresAuthentication = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
}
// Static Methods
///
/// Sets the resource to the required authentication state.
///
/// Resource name.
/// Set to true to require authenticated access; otherwise, false to require anonymous access.
///
/// This state overrides any authentication state that would otherwise be derived from .
///
public static void ResourceRequiresAuthentication(string resource, bool required)
{
s_resourceRequiresAuthentication[resource] = required;
}
#endregion
}
///
/// Represents an immutable version of .
///
public sealed class ReadonlyAuthenticationOptions
{
#region [ Members ]
// Fields
private readonly AuthenticationOptions m_authenticationOptions;
#endregion
#region [ Constructors ]
internal ReadonlyAuthenticationOptions(AuthenticationOptions authenticationOptions)
{
m_authenticationOptions = authenticationOptions;
}
#endregion
#region [ Properties ]
///
/// Gets the expression that will match paths for the resources on the web server
/// that should redirect to the when authentication fails.
///
public string AuthFailureRedirectResourceExpression => m_authenticationOptions.AuthFailureRedirectResourceExpression;
///
/// Gets the expression that will match paths for the resources on the web server
/// that can be provided without checking credentials.
///
public string AnonymousResourceExpression => m_authenticationOptions.AnonymousResourceExpression;
///
/// Gets the token used for identifying the authentication token in cookie headers.
///
public string AuthenticationToken => m_authenticationOptions.AuthenticationToken;
///
/// Gets the token used for identifying the session ID in cookie headers.
///
public string SessionToken => m_authenticationOptions.SessionToken;
///
/// Gets the token used for anti-forgery verification in HTTP request headers.
///
public string RequestVerificationToken => m_authenticationOptions.RequestVerificationToken;
///
/// Gets the login page used as a redirect location when authentication fails.
///
public string LoginPage => m_authenticationOptions.LoginPage;
///
/// Gets the page name used to test user authorization.
///
public string AuthTestPage => m_authenticationOptions.AuthTestPage;
///
/// Gets the authentication schemes to use when testing authentication with the .
///
public AuthenticationSchemes AuthenticationSchemes => m_authenticationOptions.AuthenticationSchemes;
///
/// Gets the parameter name for the that forces it to use Basic authentication
/// so that any cached browser credentials can be cleared.
///
public string ClearCredentialsParameter => m_authenticationOptions.ClearCredentialsParameter;
///
/// Gets the case-sensitive identifier that defines the protection space for this authentication.
///
public string Realm => m_authenticationOptions.Realm;
///
/// Gets any custom header to be displayed on the .
///
public string LoginHeader => m_authenticationOptions.LoginHeader;
#endregion
#region [ Methods ]
///
/// Determines whether the given resource is an authentication failure redirect resource.
///
/// Path to check as an anonymous resource.
/// true if path is an anonymous resource; otherwise, false.
public bool IsAuthFailureRedirectResource(string urlPath) => m_authenticationOptions.IsAuthFailureRedirectResource(urlPath);
///
/// Determines whether the given resource is an anonymous resource.
///
/// Path to check as an anonymous resource.
/// true if path is an anonymous resource; otherwise, false.
public bool IsAnonymousResource(string urlPath) => m_authenticationOptions.IsAnonymousResource(urlPath);
#endregion
}
}