Skip to content

Commit 6b25ce9

Browse files
authored
Add suport for asp.net core policies (CarterCommunity#193)
1 parent f9c07ec commit 6b25ce9

File tree

7 files changed

+133
-2
lines changed

7 files changed

+133
-2
lines changed

samples/CarterAndMVC/CarterAndMVC.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<TargetFramework>netcoreapp2.2</TargetFramework>
44
<AssemblyName>CarterAndMVC</AssemblyName>
55
<OutputType>Exe</OutputType>
6+
<LangVersion>latest</LangVersion>
67
</PropertyGroup>
78
<ItemGroup>
89
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />

src/Carter.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
</PropertyGroup>
1717
<ItemGroup>
1818
<PackageReference Include="FluentValidation" Version="8.1.3" />
19+
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.2.0" />
1920
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
2021
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="2.2.2" />
2122
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.1.0" />

src/CarterModuleSecurity.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System.Linq;
55
using System.Security.Claims;
66
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Authorization;
8+
using Microsoft.Extensions.DependencyInjection;
79

810
/// <summary>
911
/// <see cref="CarterModule"/> extensions to provide security mechanisms
@@ -23,6 +25,7 @@ public static void RequiresAuthentication(this CarterModule module)
2325
{
2426
context.Response.StatusCode = 401;
2527
}
28+
2629
return Task.FromResult(authenticated);
2730
};
2831
}
@@ -42,8 +45,34 @@ public static void RequiresClaims(this CarterModule module, params Predicate<Cla
4245
{
4346
context.Response.StatusCode = 401;
4447
}
48+
4549
return Task.FromResult(validClaims);
4650
};
4751
}
52+
53+
/// <summary>
54+
/// A way to require policies for your <see cref="CarterModule"/>
55+
/// </summary>
56+
/// <param name="module">Current <see cref="CarterModule"/></param>
57+
/// <param name="policyNames">The policies required for the routes in your <see cref="CarterModule"/></param>
58+
public static void RequiresPolicy(this CarterModule module, params string[] policyNames)
59+
{
60+
module.RequiresAuthentication();
61+
module.Before += async context =>
62+
{
63+
var authorizationService = context.RequestServices.GetRequiredService<IAuthorizationService>();
64+
foreach (var policy in policyNames)
65+
{
66+
var result = await authorizationService.AuthorizeAsync(context.User, policy);
67+
if (!result.Succeeded)
68+
{
69+
context.Response.StatusCode = 401;
70+
return false;
71+
}
72+
}
73+
74+
return true;
75+
};
76+
}
4877
}
4978
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Carter.Tests.Security
2+
{
3+
using Microsoft.AspNetCore.Http;
4+
5+
public class SecureMultiPolicyModule : CarterModule
6+
{
7+
public SecureMultiPolicyModule()
8+
{
9+
this.RequiresPolicy("reallysecurepolicy", "reallysecuresecondpolicy");
10+
11+
this.Get("/securemultipolicy", async (request, response, routeData) => { await response.WriteAsync("secure"); });
12+
}
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Carter.Tests.Security
2+
{
3+
using Microsoft.AspNetCore.Http;
4+
5+
public class SecureSinglePolicyModule : CarterModule
6+
{
7+
public SecureSinglePolicyModule()
8+
{
9+
this.RequiresPolicy("reallysecurepolicy");
10+
11+
this.Get("/securepolicy", async (request, response, routeData) => { await response.WriteAsync("secure"); });
12+
}
13+
}
14+
}

test/Carter.Tests/Security/SecurityTests.cs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.Builder;
99
using Microsoft.AspNetCore.Hosting;
1010
using Microsoft.AspNetCore.TestHost;
11+
using Microsoft.Extensions.DependencyInjection;
1112
using Xunit;
1213

1314
public class SecurityTests
@@ -20,9 +21,18 @@ private void ConfigureServer(bool authedUser = false, IEnumerable<Claim> claims
2021
new WebHostBuilder()
2122
.ConfigureServices(x =>
2223
{
24+
x.AddAuthorization(options =>
25+
{
26+
options.AddPolicy("reallysecurepolicy", policy => { policy.RequireClaim(ClaimTypes.Actor); });
27+
options.AddPolicy("reallysecuresecondpolicy", policy => { policy.RequireClaim(ClaimTypes.Email); });
28+
});
29+
2330
x.AddCarter(configurator: c =>
2431
c.WithModule<SecurityClaimsModule>()
25-
.WithModule<SecurityModule>());
32+
.WithModule<SecurityModule>()
33+
.WithModule<SecureSinglePolicyModule>()
34+
.WithModule<SecureMultiPolicyModule>()
35+
);
2636
})
2737
.Configure(x =>
2838
{
@@ -65,6 +75,7 @@ public async Task Should_return_200_when_valid_claims()
6575
{
6676
//Given
6777
this.ConfigureServer(true, new[] { new Claim(ClaimTypes.Actor, "Christian Slater") });
78+
6879
//When
6980
var response = await this.httpClient.GetAsync("/secureclaim");
7081
var body = response.StatusCode;
@@ -92,6 +103,7 @@ public async Task Should_return_401_when_invalid_claims()
92103
{
93104
//Given
94105
this.ConfigureServer(true, new[] { new Claim(ClaimTypes.Thumbprint, "Zebra") });
106+
95107
//When
96108
var response = await this.httpClient.GetAsync("/secureclaim");
97109
var body = response.StatusCode;
@@ -105,6 +117,7 @@ public async Task Should_return_401_when_no_claims()
105117
{
106118
//Given
107119
this.ConfigureServer(true);
120+
108121
//When
109122
var response = await this.httpClient.GetAsync("/secureclaim");
110123
var body = response.StatusCode;
@@ -118,12 +131,70 @@ public async Task Should_return_401_when_not_authed_user_but_module_requires_cla
118131
{
119132
//Given
120133
this.ConfigureServer();
134+
121135
//When
122136
var response = await this.httpClient.GetAsync("/secureclaim");
123137
var body = response.StatusCode;
124138

125139
//Then
126140
Assert.Equal(401, (int)body);
127141
}
142+
143+
[Fact]
144+
public async Task Should_return_200_when_valid_policy()
145+
{
146+
//Given
147+
this.ConfigureServer(authedUser: true, new[] { new Claim(ClaimTypes.Actor, "Nicholas Cage") });
148+
149+
//When
150+
var response = await this.httpClient.GetAsync("/securepolicy");
151+
var body = response.StatusCode;
152+
153+
//Then
154+
Assert.Equal(200, (int)body);
155+
}
156+
157+
[Fact]
158+
public async Task Should_return_401_when_invalid_policy()
159+
{
160+
//Given
161+
this.ConfigureServer(authedUser: true);
162+
163+
//When
164+
var response = await this.httpClient.GetAsync("/securepolicy");
165+
var body = response.StatusCode;
166+
167+
//Then
168+
Assert.Equal(401, (int)body);
169+
}
170+
171+
[Fact]
172+
public async Task Should_return_200_when_valid_on_multiple_policies()
173+
{
174+
//Given
175+
this.ConfigureServer(authedUser: true, new[] { new Claim(ClaimTypes.Actor, "Nicholas Cage"), new Claim(ClaimTypes.Email, "[email protected]") });
176+
177+
//When
178+
var response = await this.httpClient.GetAsync("/securemultipolicy");
179+
var body = response.StatusCode;
180+
181+
//Then
182+
Assert.Equal(200, (int)body);
183+
}
184+
185+
[Fact]
186+
public async Task Should_return_401_when_invalid_on_multiple_policies()
187+
{
188+
//Given
189+
this.ConfigureServer(authedUser: true, new[] { new Claim(ClaimTypes.Actor, "Nicholas Cage") });
190+
191+
//When
192+
var response = await this.httpClient.GetAsync("/securemultipolicy");
193+
var body = response.StatusCode;
194+
195+
//Then
196+
Assert.Equal(401, (int)body);
197+
}
198+
128199
}
129200
}

test/Directory.Build.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<Project>
22
<PropertyGroup>
3-
<TargetFramework>netcoreapp2.1</TargetFramework>
3+
<TargetFramework>netcoreapp2.2</TargetFramework>
44
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
5+
<LangVersion>latest</LangVersion>
56
</PropertyGroup>
67
<ItemGroup>
78
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.2.0" />

0 commit comments

Comments
 (0)