//****************************************************************************************************** // 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; using GSF.Web.Shared; namespace GSF.Web.Security { /// /// Represents options for authentication using . /// public sealed class AuthenticationOptions : Microsoft.Owin.Security.AuthenticationOptions { #region [ Members ] // Constants private const string AjaxTokenSuffix = "-Ajax"; /// /// Default value for . /// public const string DefaultRequestVerificationToken = "X-GSF-Verify"; /// /// Default value for . /// public const string DefaultAjaxRequestVerificationToken = DefaultRequestVerificationToken + AjaxTokenSuffix; /// /// Default value for . /// public const string DefaultAuthFailureRedirectResourceExpression = @"^/$|^/.+\.cshtml$|^/.+\.vbhtml$"; /// /// Default value for . /// public const string DefaultAnonymousResourceExpression = "^/@|^/favicon.ico$"; /// /// Default value for . /// public const string DefaultAlternateSecurityProviderResourceExpression = ""; /// /// Default value for . /// public const string DefaultLoginPage = Resources.Root + "/Security/Views/Login.cshtml"; /// /// Default value for . /// public const string DefaultLogoutPage = "/Security/logout"; /// /// 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 readonly ConcurrentDictionary m_alternateSecurityProviderResourceCache; private string m_authFailureRedirectResourceExpression; private string m_anonymousResourceExpression; private string m_alternateSecurityProviderResourceExpression; private Regex m_authFailureRedirectResources; private Regex m_alternateSecurityProviderResources; 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); m_alternateSecurityProviderResourceCache = 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 => 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 should user the Alternate SecurityProvider. /// public string AlternateSecurityProviderResourceExpression { get => m_alternateSecurityProviderResourceExpression; set { m_alternateSecurityProviderResourceExpression = value; m_alternateSecurityProviderResources = new Regex(m_alternateSecurityProviderResourceExpression, 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 => 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 token to specify when using AJAX for request verification tokens. /// public string AjaxRequestVerificationToken => RequestVerificationToken + AjaxTokenSuffix; /// /// Gets or sets the login page used as a redirect location when authentication fails. /// public string LoginPage { get; set; } = DefaultLoginPage; /// /// Gets or sets the path for the logout page. /// public string LogoutPage { get; set; } = DefaultLogoutPage; /// /// 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 => 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 authentication failure redirect resource. /// true if path is an authentication failure redirect resource; otherwise, false. public bool IsAuthFailureRedirectResource(string urlPath) { if (m_authFailureRedirectResourceExpression is null) AuthFailureRedirectResourceExpression = DefaultAuthFailureRedirectResourceExpression; return m_authFailureRedirectResourceCache.GetOrAdd(urlPath, m_authFailureRedirectResources.IsMatch); } /// /// Determines whether the given resource is a alternate securtityProvider resource. /// /// Path to check as an alternate securtityProvider resource. /// true if path is an alternate securtityProvider resource; otherwise, false. public bool IsAlternateSecurityProviderResource(string urlPath) { if (m_alternateSecurityProviderResources is null) AlternateSecurityProviderResourceExpression = DefaultAlternateSecurityProviderResourceExpression ; if (string.IsNullOrEmpty(m_alternateSecurityProviderResourceExpression)) return m_alternateSecurityProviderResourceCache.GetOrAdd(urlPath, false); return m_alternateSecurityProviderResourceCache.GetOrAdd(urlPath, m_alternateSecurityProviderResources.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 (m_anonymousResourceExpression is null) AnonymousResourceExpression = DefaultAnonymousResourceExpression; return m_anonymousResourceCache.GetOrAdd(urlPath, path => { string resource = path; // 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 bool 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 expression that will match paths for the resources on the web server /// that should use the alternate SecurityProvider. /// public string AlternateSecurityProviderResourceExpression => m_authenticationOptions.AlternateSecurityProviderResourceExpression; /// /// 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 token to specify when using AJAX for request verification tokens. /// public string AjaxRequestVerificationToken => m_authenticationOptions.AjaxRequestVerificationToken; /// /// Gets the login page used as a redirect location when authentication fails. /// public string LoginPage => m_authenticationOptions.LoginPage; /// /// Gets the path for the logout page. /// public string LogoutPage => m_authenticationOptions.LogoutPage; /// /// 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 authentication failure redirect resource. /// true if path is an authentication failure redirect 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); /// /// Determines whether the given resource is using the alternate SecuityProvider. /// /// Path to check as an alternative SecurityProvider resource. /// true if path is an alternate Security Provider resource; otherwise, false. public bool IsAlternateSecurityProviderResource(string urlPath) => m_authenticationOptions.IsAlternateSecurityProviderResource(urlPath); #endregion } }