React to Security Auth changes

This commit is contained in:
Hao Kung 2015-01-15 23:41:52 -08:00
parent 262bb9a732
commit 7b2fb55ef6
11 changed files with 371 additions and 80 deletions

View File

@ -50,14 +50,14 @@ namespace MvcSample.Web
return new ChallengeResult();
}
[Authorize("Permission", "CanViewPage")]
[Authorize("CanViewPage")]
public ActionResult NotGrantedClaim(int age = 20, string userName = "SampleUser")
{
return Index(age, userName);
}
[FakeUser]
[Authorize("Permission", "CanViewPage", "CanViewAnything")]
[Authorize("CanViewAnything")]
public ActionResult AllGranted(int age = 20, string userName = "SampleUser")
{
return Index(age, userName);

View File

@ -3,10 +3,12 @@
using System;
using System.IO;
using System.Security.Claims;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Security;
using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection;
using MvcSample.Web.Filters;
@ -38,6 +40,19 @@ namespace MvcSample.Web
app.UseServices(services =>
{
services.ConfigureAuthorization(auth =>
{
auth.AddPolicy("CanViewPage",
new AuthorizationPolicyBuilder()
.RequiresClaim("Permission", "CanViewPage", "CanViewAnything").Build());
auth.AddPolicy("CanViewAnything",
new AuthorizationPolicyBuilder()
.RequiresClaim("Permission", "CanViewAnything").Build());
// This policy basically requires that the auth type is present
var basicPolicy = new AuthorizationPolicyBuilder("Basic").RequiresClaim(ClaimTypes.NameIdentifier);
auth.AddPolicy("RequireBasic", basicPolicy.Build());
});
services.AddMvc();
services.AddSingleton<PassThroughAttribute>();
services.AddSingleton<UserNameService>();

View File

@ -2,9 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Security;
@ -14,69 +11,74 @@ namespace Microsoft.AspNet.Mvc
{
public class AuthorizeAttribute : AuthorizationFilterAttribute
{
protected Claim[] _claims;
private string _roles;
private string[] _rolesSplit;
public AuthorizeAttribute()
public AuthorizeAttribute() { }
public AuthorizeAttribute(string policy)
{
_claims = new Claim[0];
Policy = policy;
}
public AuthorizeAttribute([NotNull]IEnumerable<Claim> claims)
{
_claims = claims.ToArray();
}
public string Policy { get; set; }
public AuthorizeAttribute(string claimType, string claimValue)
public string Roles
{
_claims = new[] { new Claim(claimType, claimValue) };
}
public AuthorizeAttribute(string claimType, string claimValue, params string[] otherClaimValues)
: this(claimType, claimValue)
{
if (otherClaimValues.Length > 0)
get { return _roles; }
set
{
_claims = _claims.Concat(otherClaimValues.Select(claim => new Claim(claimType, claim))).ToArray();
_roles = value;
if (string.IsNullOrWhiteSpace(_roles))
{
_rolesSplit = null;
}
else
{
_rolesSplit = _roles.Split(',');
}
}
}
public override async Task OnAuthorizationAsync([NotNull] AuthorizationContext context)
{
var httpContext = context.HttpContext;
var user = httpContext.User;
// when no claims are specified, we just need to ensure the user is authenticated
if (_claims.Length == 0)
// Allow Anonymous skips all authorization
if (HasAllowAnonymous(context))
{
var userIsAnonymous =
user == null ||
user.Identity == null ||
!user.Identity.IsAuthenticated;
return;
}
if (userIsAnonymous && !HasAllowAnonymous(context))
var authService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();
// Build a policy for the requested roles if specified
if (_rolesSplit != null)
{
var rolesPolicy = new AuthorizationPolicyBuilder();
rolesPolicy.RequiresRole(_rolesSplit);
if (!await authService.AuthorizeAsync(rolesPolicy.Build(), httpContext, context))
{
Fail(context);
return;
}
}
else
var authorized = (Policy == null)
// [Authorize] with no policy just requires any authenticated user
? await authService.AuthorizeAsync(BuildAnyAuthorizedUserPolicy(), httpContext, context)
: await authService.AuthorizeAsync(Policy, httpContext, context);
if (!authorized)
{
var authorizationService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();
if (authorizationService == null)
{
throw new InvalidOperationException(
Resources.AuthorizeAttribute_AuthorizationServiceMustBeDefined);
}
var authorized = await authorizationService.AuthorizeAsync(_claims, user);
if (!authorized)
{
Fail(context);
}
Fail(context);
}
}
private static AuthorizationPolicy BuildAnyAuthorizedUserPolicy()
{
return new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
}
public sealed override void OnAuthorization([NotNull] AuthorizationContext context)
{
// The async filter will be called by the filter pipeline.

View File

@ -1,9 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Security.DataProtection;
using Microsoft.AspNet.Security;
using Microsoft.Framework.ConfigurationModel;
namespace Microsoft.Framework.DependencyInjection
@ -23,10 +26,12 @@ namespace Microsoft.Framework.DependencyInjection
services.AddDataProtection(configuration);
services.AddRouting(configuration);
services.AddScopedInstance(configuration);
services.AddAuthorization(configuration);
services.Configure<RouteOptions>(routeOptions =>
routeOptions.ConstraintMap
.Add("exists",
typeof(KnownRouteValueConstraint)));
}
}
}

View File

@ -151,7 +151,6 @@ namespace Microsoft.AspNet.Mvc
// Security and Authorization
yield return describe.Transient<IAuthorizationService, DefaultAuthorizationService>();
yield return describe.Singleton<IClaimUidExtractor, DefaultClaimUidExtractor>();
yield return describe.Singleton<AntiForgery, AntiForgery>();
yield return describe.Singleton<IAntiForgeryAdditionalDataProvider,

View File

@ -1,8 +1,6 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Security;
using Microsoft.Framework.DependencyInjection;
@ -17,11 +15,15 @@ namespace Microsoft.AspNet.Mvc.Core.Test
public async Task Invoke_ValidClaimShouldNotFail()
{
// Arrange
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
var authorizeAttribute = new AuthorizeAttribute("Permission", "CanViewPage");
var authorizeAttribute = new AuthorizeAttribute("CanViewPage");
var authorizationContext = GetAuthorizationContext(services =>
services.AddInstance<IAuthorizationService>(authorizationService)
);
{
services.AddAuthorization(null, options =>
{
var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewPage");
options.AddPolicy("CanViewPage", policy.Build());
});
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
@ -34,12 +36,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test
public async Task Invoke_EmptyClaimsShouldRejectAnonymousUser()
{
// Arrange
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
var authorizationOptions = new AuthorizationOptions();
var authorizeAttribute = new AuthorizeAttribute();
var authorizationContext = GetAuthorizationContext(services =>
services.AddInstance<IAuthorizationService>(authorizationService),
anonymous: true
);
services.AddAuthorization(),
anonymous: true);
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
@ -52,12 +53,13 @@ namespace Microsoft.AspNet.Mvc.Core.Test
public async Task Invoke_EmptyClaimsWithAllowAnonymousAttributeShouldNotRejectAnonymousUser()
{
// Arrange
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
var authorizeAttribute = new AuthorizeAttribute();
var authorizationContext = GetAuthorizationContext(services =>
services.AddInstance<IAuthorizationService>(authorizationService),
anonymous: true
);
{
services.AddAuthorization();
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
},
anonymous: true);
authorizationContext.Filters.Add(new AllowAnonymousAttribute());
@ -72,11 +74,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test
public async Task Invoke_EmptyClaimsShouldAuthorizeAuthenticatedUser()
{
// Arrange
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
var authorizeAttribute = new AuthorizeAttribute();
var authorizationContext = GetAuthorizationContext(services =>
services.AddInstance<IAuthorizationService>(authorizationService)
);
{
services.AddAuthorization();
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
@ -89,11 +92,16 @@ namespace Microsoft.AspNet.Mvc.Core.Test
public async Task Invoke_SingleValidClaimShouldSucceed()
{
// Arrange
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
var authorizeAttribute = new AuthorizeAttribute("Permission", "CanViewComment", "CanViewPage");
var authorizeAttribute = new AuthorizeAttribute("CanViewCommentOrPage");
var authorizationContext = GetAuthorizationContext(services =>
services.AddInstance<IAuthorizationService>(authorizationService)
);
{
services.AddAuthorization(null, options =>
{
var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewComment", "CanViewPage");
options.AddPolicy("CanViewCommentOrPage", policy.Build());
});
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
@ -102,15 +110,96 @@ namespace Microsoft.AspNet.Mvc.Core.Test
Assert.Null(authorizationContext.Result);
}
[Fact]
public async Task Invoke_RequireAdminRoleShouldFailWithNoHandlers()
{
// Arrange
var authorizeAttribute = new AuthorizeAttribute { Roles = "Administrator" };
var authorizationContext = GetAuthorizationContext(services =>
{
services.AddOptions();
services.AddTransient<IAuthorizationService, DefaultAuthorizationService>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
// Assert
Assert.NotNull(authorizationContext.Result);
}
[Fact]
public async Task Invoke_RequireAdminAndUserRoleWithNoPolicyShouldSucceed()
{
// Arrange
var authorizeAttribute = new AuthorizeAttribute { Roles = "Administrator,User" };
var authorizationContext = GetAuthorizationContext(services =>
{
services.AddAuthorization();
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
// Assert
Assert.Null(authorizationContext.Result);
}
[Fact]
public async Task Invoke_RequireUnknownRoleShouldFail()
{
// Arrange
var authorizeAttribute = new AuthorizeAttribute { Roles = "Wut" };
var authorizationContext = GetAuthorizationContext(services =>
{
services.AddAuthorization();
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
// Assert
Assert.NotNull(authorizationContext.Result);
}
[Fact]
public async Task Invoke_RequireAdminRoleButFailPolicyShouldFail()
{
// Arrange
var authorizeAttribute = new AuthorizeAttribute { Roles = "Administrator", Policy = "Basic" };
var authorizationContext = GetAuthorizationContext(services =>
{
services.AddAuthorization(null, options =>
{
var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewComment");
options.AddPolicy("CanViewComment", policy.Build());
});
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
// Assert
Assert.NotNull(authorizationContext.Result);
}
[Fact]
public async Task Invoke_InvalidClaimShouldFail()
{
// Arrange
var authorizationService = new DefaultAuthorizationService(Enumerable.Empty<IAuthorizationPolicy>());
var authorizeAttribute = new AuthorizeAttribute("Permission", "CanViewComment");
var authorizeAttribute = new AuthorizeAttribute("CanViewComment");
var authorizationContext = GetAuthorizationContext(services =>
services.AddInstance<IAuthorizationService>(authorizationService)
);
{
services.AddAuthorization(null, options =>
{
var policy = new AuthorizationPolicyBuilder().RequiresClaim("Permission", "CanViewComment");
options.AddPolicy("CanViewComment", policy.Build());
});
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
@ -126,16 +215,16 @@ namespace Microsoft.AspNet.Mvc.Core.Test
bool authorizationServiceIsCalled = false;
var authorizationService = new Mock<IAuthorizationService>();
authorizationService
.Setup(x => x.AuthorizeAsync(Enumerable.Empty<Claim>(), null, null))
.Setup(x => x.AuthorizeAsync("CanViewComment", null, null))
.Returns(() =>
{
authorizationServiceIsCalled = true;
return Task.FromResult(true);
});
var authorizeAttribute = new AuthorizeAttribute("Permission", "CanViewComment");
var authorizeAttribute = new AuthorizeAttribute("CanViewComment");
var authorizationContext = GetAuthorizationContext(services =>
services.AddInstance<IAuthorizationService>(authorizationService.Object)
services.AddInstance(authorizationService.Object)
);
authorizationContext.Result = new HttpStatusCodeResult(401);
@ -148,14 +237,60 @@ namespace Microsoft.AspNet.Mvc.Core.Test
}
[Fact]
public async Task Invoke_NullPoliciesShouldNotFail()
public async Task Invoke_FailWhenLookingForClaimInOtherIdentity()
{
// Arrange
var authorizationService = new DefaultAuthorizationService(policies: null);
var authorizeAttribute = new AuthorizeAttribute("Permission", "CanViewPage");
var authorizeAttribute = new AuthorizeAttribute("CanViewComment");
var authorizationContext = GetAuthorizationContext(services =>
services.AddInstance<IAuthorizationService>(authorizationService)
);
{
services.AddAuthorization(null, options =>
{
var policy = new AuthorizationPolicyBuilder("Bearer").RequiresClaim("Permission", "CanViewComment");
options.AddPolicy("CanViewComment", policy.Build());
});
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
// Assert
Assert.NotNull(authorizationContext.Result);
}
[Fact]
public async Task Invoke_CanLookingForClaimsInMultipleIdentities()
{
// Arrange
var authorizeAttribute = new AuthorizeAttribute("CanViewCommentCupBearer");
var authorizationContext = GetAuthorizationContext(services =>
{
services.AddAuthorization(null, options =>
{
var policy = new AuthorizationPolicyBuilder("Basic", "Bearer")
.RequiresClaim("Permission", "CanViewComment")
.RequiresClaim("Permission", "CupBearer");
options.AddPolicy("CanViewComment", policy.Build());
});
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);
// Assert
Assert.NotNull(authorizationContext.Result);
}
public async Task Invoke_NoPoliciesShouldNotFail()
{
// Arrange
var authorizeAttribute = new AuthorizeAttribute("CanViewPage");
var authorizationContext = GetAuthorizationContext(services =>
{
services.AddAuthorization();
services.AddTransient<IAuthorizationHandler, DenyAnonymousAuthorizationHandler>();
});
// Act
await authorizeAttribute.OnAuthorizationAsync(authorizationContext);

View File

@ -21,9 +21,18 @@ namespace Microsoft.AspNet.Mvc.Core.Test
new Claim[] {
new Claim("Permission", "CanViewPage"),
new Claim(ClaimTypes.Role, "Administrator"),
new Claim(ClaimTypes.Role, "User"),
new Claim(ClaimTypes.NameIdentifier, "John")},
"Basic"));
validUser.AddIdentity(
new ClaimsIdentity(
new Claim[] {
new Claim("Permission", "CupBearer"),
new Claim(ClaimTypes.Role, "Token"),
new Claim(ClaimTypes.NameIdentifier, "John Bear")},
"Bearer"));
// ServiceProvider
var serviceCollection = new ServiceCollection();
if (registerServices != null)

View File

@ -122,6 +122,37 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("Hello World!", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task AllowAnonymousOverridesAuthorize()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync(
"http://localhost/AuthorizeUser/AlwaysCanCallAllowAnonymous");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello World!", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task ImpossiblePolicyFailsAuthorize()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await client.GetAsync(
"http://localhost/AuthorizeUser/Impossible");
// Assert
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task ServiceFilterUsesRegisteredServicesAsFilter()
{

View File

@ -0,0 +1,61 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Claims;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Security;
using Microsoft.AspNet.Security.Infrastructure;
using Microsoft.Framework.OptionsModel;
namespace FiltersWebSite
{
public class BasicOptions : AuthenticationOptions
{
public BasicOptions()
{
AuthenticationType = "Basic";
AuthenticationMode = AuthenticationMode.Passive;
}
}
public class AuthorizeBasicMiddleware : AuthenticationMiddleware<BasicOptions>
{
public AuthorizeBasicMiddleware(
RequestDelegate next,
IServiceProvider services,
IOptions<BasicOptions> options) :
base(next, services, options, null)
{ }
protected override AuthenticationHandler<BasicOptions> CreateHandler()
{
return new BasicAuthenticationHandler();
}
}
public class BasicAuthenticationHandler : AuthenticationHandler<BasicOptions>
{
protected override void ApplyResponseChallenge()
{
}
protected override void ApplyResponseGrant()
{
}
protected override AuthenticationTicket AuthenticateCore()
{
var id = new ClaimsIdentity(
new Claim[] {
new Claim("Permission", "CanViewPage"),
new Claim(ClaimTypes.Role, "Administrator"),
new Claim(ClaimTypes.NameIdentifier, "John")},
"Basic");
return new AuthenticationTicket(id, new AuthenticationProperties());
}
}
}

View File

@ -6,12 +6,27 @@ using Microsoft.AspNet.Mvc;
namespace FiltersWebSite
{
[AuthorizeUser]
[Authorize("RequireBasic")]
public class AuthorizeUserController : Controller
{
[Authorize("Permission", "CanViewPage")]
[Authorize("CanViewPage")]
public string ReturnHelloWorldOnlyForAuthorizedUser()
{
return "Hello World!";
}
[Authorize("Impossible")]
[AllowAnonymous]
public string AlwaysCanCallAllowAnonymous()
{
return "Hello World!";
}
[Authorize("Impossible")]
public string Impossible()
{
return "Hello World!";
}
}
}

View File

@ -1,8 +1,10 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Claims;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Security;
using Microsoft.Framework.DependencyInjection;
namespace FiltersWebSite
@ -16,6 +18,21 @@ namespace FiltersWebSite
app.UseServices(services =>
{
services.AddMvc(configuration);
services.Configure<AuthorizationOptions>(options =>
{
// This policy cannot succeed since it has no requirements
options.AddPolicy("Impossible",
new AuthorizationPolicyBuilder()
.Build());
options.AddPolicy("RequireBasic",
new AuthorizationPolicyBuilder("Basic")
.RequiresClaim(ClaimTypes.NameIdentifier)
.Build());
options.AddPolicy("CanViewPage",
new AuthorizationPolicyBuilder()
.RequiresClaim("Permission", "CanViewPage")
.Build());
});
services.AddSingleton<RandomNumberFilter>();
services.AddSingleton<RandomNumberService>();
@ -31,6 +48,8 @@ namespace FiltersWebSite
app.UseErrorReporter();
app.UseMiddleware<AuthorizeBasicMiddleware>();
app.UseMvc();
}
}