// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Principal; using System.Threading; using System.Web.Http.Controllers; using Microsoft.TestCommon; using Moq; using Xunit; using Xunit.Extensions; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Http { public class AuthorizeAttributeTest : IDisposable { private readonly Mock _actionDescriptorMock = new Mock() { CallBase = true }; private readonly Collection _allowAnonymousAttributeCollection = new Collection(new AllowAnonymousAttribute[] { new AllowAnonymousAttribute() }); private readonly MockableAuthorizeAttribute _attribute; private readonly Mock _attributeMock = new Mock() { CallBase = true }; private readonly Mock _controllerDescriptorMock = new Mock() { CallBase = true }; private readonly HttpControllerContext _controllerContext; private readonly HttpActionContext _actionContext; private readonly Mock _principalMock = new Mock(); private readonly IPrincipal _originalPrincipal; private readonly HttpRequestMessage _request = new HttpRequestMessage(); public AuthorizeAttributeTest() { _attribute = _attributeMock.Object; _controllerContext = new Mock() { CallBase = true }.Object; _controllerDescriptorMock.Setup(cd => cd.GetCustomAttributes()).Returns(new Collection(Enumerable.Empty().ToList())); _actionDescriptorMock.Setup(ad => ad.GetCustomAttributes()).Returns(new Collection(Enumerable.Empty().ToList())); _controllerContext.ControllerDescriptor = _controllerDescriptorMock.Object; _controllerContext.Request = _request; _actionContext = ContextUtil.CreateActionContext(_controllerContext, _actionDescriptorMock.Object); _originalPrincipal = Thread.CurrentPrincipal; Thread.CurrentPrincipal = _principalMock.Object; } public void Dispose() { Thread.CurrentPrincipal = _originalPrincipal; } [Fact] public void Roles_Property() { AuthorizeAttribute attribute = new AuthorizeAttribute(); Assert.Reflection.StringProperty(attribute, a => a.Roles, expectedDefaultValue: String.Empty); } [Fact] public void Users_Property() { AuthorizeAttribute attribute = new AuthorizeAttribute(); Assert.Reflection.StringProperty(attribute, a => a.Users, expectedDefaultValue: String.Empty); } [Fact] public void AllowMultiple_ReturnsTrue() { Assert.True(_attribute.AllowMultiple); } [Fact] public void TypeId_ReturnsUniqueInstances() { var attribute1 = new AuthorizeAttribute(); var attribute2 = new AuthorizeAttribute(); Assert.NotSame(attribute1.TypeId, attribute2.TypeId); } [Fact] public void OnAuthorization_IfContextParameterIsNull_ThrowsException() { Assert.ThrowsArgumentNull(() => { _attribute.OnAuthorization(actionContext: null); }, "actionContext"); } [Fact] public void OnAuthorization_IfUserIsAuthenticated_DoesNotShortCircuitRequest() { _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true); _attribute.OnAuthorization(_actionContext); Assert.Null(_actionContext.Response); } [Fact] public void OnAuthorization_IfThreadDoesNotContainPrincipal_DoesShortCircuitRequest() { Thread.CurrentPrincipal = null; _attribute.OnAuthorization(_actionContext); AssertUnauthorizedRequestSet(_actionContext); } [Fact] public void OnAuthorization_IfUserIsNotAuthenticated_DoesShortCircuitRequest() { _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(false).Verifiable(); _attribute.OnAuthorization(_actionContext); AssertUnauthorizedRequestSet(_actionContext); _principalMock.Verify(); } [Fact] public void OnAuthorization_IfUserIsNotInUsersCollection_DoesShortCircuitRequest() { _attribute.Users = "John"; _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true).Verifiable(); _principalMock.Setup(p => p.Identity.Name).Returns("Mary").Verifiable(); _attribute.OnAuthorization(_actionContext); AssertUnauthorizedRequestSet(_actionContext); _principalMock.Verify(); } [Fact] public void OnAuthorization_IfUserIsInUsersCollection_DoesNotShortCircuitRequest() { _attribute.Users = " John , Mary "; _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true).Verifiable(); _principalMock.Setup(p => p.Identity.Name).Returns("Mary").Verifiable(); _attribute.OnAuthorization(_actionContext); Assert.Null(_actionContext.Response); _principalMock.Verify(); } [Fact] public void OnAuthorization_IfUserIsNotInRolesCollection_DoesShortCircuitRequest() { _attribute.Users = " John , Mary "; _attribute.Roles = "Administrators,PowerUsers"; _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true).Verifiable(); _principalMock.Setup(p => p.Identity.Name).Returns("Mary").Verifiable(); _principalMock.Setup(p => p.IsInRole("Administrators")).Returns(false).Verifiable(); _principalMock.Setup(p => p.IsInRole("PowerUsers")).Returns(false).Verifiable(); _attribute.OnAuthorization(_actionContext); AssertUnauthorizedRequestSet(_actionContext); _principalMock.Verify(); } [Fact] public void OnAuthorization_IfUserIsInRolesCollection_DoesNotShortCircuitRequest() { _attribute.Users = " John , Mary "; _attribute.Roles = "Administrators,PowerUsers"; _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(true).Verifiable(); _principalMock.Setup(p => p.Identity.Name).Returns("Mary").Verifiable(); _principalMock.Setup(p => p.IsInRole("Administrators")).Returns(false).Verifiable(); _principalMock.Setup(p => p.IsInRole("PowerUsers")).Returns(true).Verifiable(); _attribute.OnAuthorization(_actionContext); Assert.Null(_actionContext.Response); _principalMock.Verify(); } [Fact] public void OnAuthorization_IfActionDescriptorIsMarkedWithAllowAnonymousAttribute_DoesNotShortCircuitResponse() { _actionDescriptorMock.Setup(ad => ad.GetCustomAttributes()).Returns(_allowAnonymousAttributeCollection); Mock authorizeAttributeMock = new Mock() { CallBase = true }; AuthorizeAttribute attribute = authorizeAttributeMock.Object; attribute.OnAuthorization(_actionContext); Assert.Null(_actionContext.Response); } [Fact] public void OnAuthorization_IfControllerDescriptorIsMarkedWithAllowAnonymousAttribute_DoesNotShortCircuitResponse() { _controllerDescriptorMock.Setup(ad => ad.GetCustomAttributes()).Returns(_allowAnonymousAttributeCollection); Mock authorizeAttributeMock = new Mock() { CallBase = true }; AuthorizeAttribute attribute = authorizeAttributeMock.Object; attribute.OnAuthorization(_actionContext); Assert.Null(_actionContext.Response); } [Fact] public void OnAuthorization_IfRequestNotAuthorized_CallsHandleUnauthorizedRequest() { Mock authorizeAttributeMock = new Mock() { CallBase = true }; _principalMock.Setup(p => p.Identity.IsAuthenticated).Returns(false); authorizeAttributeMock.Setup(a => a.HandleUnauthorizedRequestPublic(_actionContext)).Verifiable(); AuthorizeAttribute attribute = authorizeAttributeMock.Object; attribute.OnAuthorization(_actionContext); authorizeAttributeMock.Verify(); } [Fact] public void HandleUnauthorizedRequest_IfContextParameterIsNull_ThrowsArgumentNullException() { Assert.ThrowsArgumentNull(() => { _attribute.HandleUnauthorizedRequestPublic(context: null); }, "actionContext"); } [Fact] public void HandleUnauthorizedRequest_SetsResponseWithUnauthorizedStatusCode() { _attribute.HandleUnauthorizedRequestPublic(_actionContext); Assert.NotNull(_actionContext.Response); Assert.Equal(HttpStatusCode.Unauthorized, _actionContext.Response.StatusCode); Assert.Same(_request, _actionContext.Response.RequestMessage); } [Theory] [PropertyData("SplitStringTestData")] public void SplitString_SplitsOnCommaAndTrimsWhitespaceAndIgnoresEmptyStrings(string input, params string[] expectedResult) { string[] result = AuthorizeAttribute.SplitString(input); Assert.Equal(expectedResult, result); } public static IEnumerable SplitStringTestData { get { return new ParamsTheoryDataSet() { { null }, { String.Empty }, { " " }, { " A ", "A" }, { " A, B ", "A", "B" }, { " , A, ,B, ", "A", "B" }, { " A B ", "A B" }, }; } } [CLSCompliant(false)] public class ParamsTheoryDataSet : TheoryDataSet { public void Add(TParam1 p1, params TParam2[] p2) { AddItem(p1, p2); } } private static void AssertUnauthorizedRequestSet(HttpActionContext actionContext) { Assert.NotNull(actionContext.Response); Assert.Equal(HttpStatusCode.Unauthorized, actionContext.Response.StatusCode); Assert.Same(actionContext.ControllerContext.Request, actionContext.Response.RequestMessage); } public class MockableAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(HttpActionContext context) { HandleUnauthorizedRequestPublic(context); } public virtual void HandleUnauthorizedRequestPublic(HttpActionContext context) { base.HandleUnauthorizedRequest(context); } } } }