// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Principal; using System.Threading; using System.Web.Http.Controllers; using System.Web.Http.Filters; namespace System.Web.Http { /// /// An authorization filter that verifies the request's . /// /// You can declare multiple of these attributes per action. You can also use /// to disable authorization for a specific action. /// [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want to support extensibility")] [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] public class AuthorizeAttribute : AuthorizationFilterAttribute { private static readonly string[] _emptyArray = new string[0]; private readonly object _typeId = new object(); private string _roles; private string[] _rolesSplit = _emptyArray; private string _users; private string[] _usersSplit = _emptyArray; /// /// Gets or sets the authorized roles. /// /// /// The roles string. /// /// Multiple role names can be specified using the comma character as a separator. public string Roles { get { return _roles ?? String.Empty; } set { _roles = value; _rolesSplit = SplitString(value); } } /// /// Gets a unique identifier for this . /// /// The unique identifier for the attribute. public override object TypeId { get { return _typeId; } } /// /// Gets or sets the authorized users. /// /// /// The users string. /// /// Multiple role names can be specified using the comma character as a separator. public string Users { get { return _users ?? String.Empty; } set { _users = value; _usersSplit = SplitString(value); } } /// /// Determines whether access for this particular request is authorized. This method uses the user /// returned via . Authorization is denied if the user is not authenticated, /// the user is not in the authorized group of (if defined), or if the user is not in any of the authorized /// (if defined). /// /// true if access is authorized; otherwise false. private bool AuthorizeCore() { IPrincipal user = Thread.CurrentPrincipal; if (user == null || !user.Identity.IsAuthenticated) { return false; } if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) { return false; } if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) { return false; } return true; } /// /// Called when an action is being authorized. This method uses the user /// returned via . Authorization is denied if /// - the request is not associated with any user. /// - the user is not authenticated, /// - the user is authenticated but is not in the authorized group of (if defined), or if the user /// is not in any of the authorized (if defined). /// /// If authorization is denied then this method will invoke to process the unauthorized request. /// /// You can use to cause authorization checks to be skipped for a particular /// action or controller. /// The context. /// The context parameter is null. public override void OnAuthorization(HttpActionContext actionContext) { if (actionContext == null) { throw Error.ArgumentNull("actionContext"); } if (SkipAuthorization(actionContext)) { return; } if (!AuthorizeCore()) { HandleUnauthorizedRequest(actionContext); } } /// /// Processes requests that fail authorization. This default implementation creates a new response with the /// Unauthorized status code. Override this method to provide your own handling for unauthorized requests. /// /// The context. protected virtual void HandleUnauthorizedRequest(HttpActionContext actionContext) { if (actionContext == null) { throw Error.ArgumentNull("actionContext"); } actionContext.Response = actionContext.ControllerContext.Request.CreateResponse(HttpStatusCode.Unauthorized); } private static bool SkipAuthorization(HttpActionContext actionContext) { Contract.Assert(actionContext != null); return actionContext.ActionDescriptor.GetCustomAttributes().Any() || actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes().Any(); } /// /// Splits the string on commas and removes any leading/trailing whitespace from each result item. /// /// The input string. /// An array of strings parsed from the input string. internal static string[] SplitString(string original) { if (String.IsNullOrEmpty(original)) { return _emptyArray; } var split = from piece in original.Split(',') let trimmed = piece.Trim() where !String.IsNullOrEmpty(trimmed) select trimmed; return split.ToArray(); } } }