// 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();
}
}
}