Skip to content

Commit 03f5f24

Browse files
authored
Merge pull request #4 from fmichellonet/features/tests
Authorize on Type and AllowAnonymous
2 parents d3a4fc2 + 5df2d76 commit 03f5f24

File tree

7 files changed

+219
-24
lines changed

7 files changed

+219
-24
lines changed

.github/workflows/ci.yml

+10-5
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ jobs:
2020
uses: actions/setup-dotnet@v1
2121
with:
2222
dotnet-version: 3.1.404
23-
- name: Build with dotnet
24-
run: dotnet build --configuration Release
23+
# restore dependencies
24+
- name: Install dependencies
25+
run: dotnet restore
2526
working-directory: .\src
26-
# - name: Test
27-
# run: dotnet test
28-
# working-directory: .\src
27+
# build
28+
- name: Build
29+
run: dotnet build --no-restore --configuration Release
30+
working-directory: .\src
31+
- name: Test
32+
run: dotnet test
33+
working-directory: .\src
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="FluentAssertions" Version="5.10.3" />
9+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
10+
<PackageReference Include="NUnit" Version="3.13.1" />
11+
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\AzureFunctions.Extensions.OpenIDConnect\AzureFunctions.Extensions.OpenIDConnect.csproj" />
16+
</ItemGroup>
17+
18+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
using System.Threading.Tasks;
2+
using FluentAssertions;
3+
using Microsoft.AspNetCore.Authorization;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.Azure.WebJobs;
7+
using Microsoft.Azure.WebJobs.Extensions.Http;
8+
using Microsoft.Extensions.Logging;
9+
using NUnit.Framework;
10+
11+
namespace AzureFunctions.Extensions.OpenIDConnect.Tests
12+
{
13+
using System;
14+
using System.Collections.Generic;
15+
16+
[TestFixture]
17+
public class RouteGuardianShould
18+
{
19+
[Test]
20+
public async Task Not_Authorize_When_Not_HttpTrigger()
21+
{
22+
// Arrange
23+
var guardian = new RouteGuardian(() => new List<Type>{ typeof(Not_HttpTrigger) });
24+
25+
// Act
26+
var result = await guardian.ShouldAuthorize("Not_HttpTrigger");
27+
28+
// Assert
29+
result.Should().Be(false);
30+
}
31+
32+
[Test]
33+
public async Task Not_Authorize_When_No_Authorize_Attribute_On_Method_And_Type()
34+
{
35+
// Arrange
36+
var guardian = new RouteGuardian(() => new List<Type> { typeof(No_Authorize_Attribute_On_Method_And_Type) });
37+
38+
// Act
39+
var result = await guardian.ShouldAuthorize("No_Authorize_Attribute_On_Method_And_Type");
40+
41+
// Assert
42+
result.Should().Be(false);
43+
}
44+
45+
[Test]
46+
public async Task Authorize_When_Authorize_Attribute_Is_On_Method()
47+
{
48+
// Arrange
49+
var guardian = new RouteGuardian(() => new List<Type> { typeof(Authorize_Attribute_Is_On_Method) });
50+
51+
// Act
52+
var result = await guardian.ShouldAuthorize("Authorize_Attribute_Is_On_Method");
53+
54+
// Assert
55+
result.Should().Be(true);
56+
}
57+
58+
[Test]
59+
public async Task Authorize_When_Authorize_Attribute_Is_On_Class()
60+
{
61+
// Arrange
62+
var guardian = new RouteGuardian(() => new List<Type> { typeof(Authorize_Attribute_Is_On_Class) });
63+
64+
// Act
65+
var result = await guardian.ShouldAuthorize("Authorize_Attribute_Is_On_Class");
66+
67+
// Assert
68+
result.Should().Be(true);
69+
}
70+
71+
[Test]
72+
public async Task NotAuthorize_When_Authorize_Attribute_Is_On_Class_But_AllowAnonimous_On_Method()
73+
{
74+
// Arrange
75+
var guardian = new RouteGuardian(() => new List<Type> { typeof(Attribute_Is_On_Class_But_AllowAnonimous_On_Method) });
76+
77+
// Act
78+
var result = await guardian.ShouldAuthorize("Attribute_Is_On_Class_But_AllowAnonimous_On_Method");
79+
80+
// Assert
81+
result.Should().Be(false);
82+
}
83+
84+
85+
86+
internal class Not_HttpTrigger
87+
{
88+
[Authorize]
89+
[FunctionName("Not_HttpTrigger")]
90+
public IActionResult Run(HttpRequest req, ILogger log)
91+
{
92+
var responseMessage = "Hello. This HTTP triggered function is protected.";
93+
94+
return new OkObjectResult(responseMessage);
95+
}
96+
}
97+
98+
internal class No_Authorize_Attribute_On_Method_And_Type
99+
{
100+
[FunctionName("No_Authorize_Attribute_On_Method_And_Type")]
101+
public IActionResult Run(
102+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
103+
{
104+
var responseMessage = "Hello. This HTTP triggered function is protected.";
105+
106+
return new OkObjectResult(responseMessage);
107+
}
108+
}
109+
110+
internal class Authorize_Attribute_Is_On_Method
111+
{
112+
[Authorize]
113+
[FunctionName("Authorize_Attribute_Is_On_Method")]
114+
public IActionResult Run(
115+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
116+
{
117+
var responseMessage = "Hello. This HTTP triggered function is protected.";
118+
119+
return new OkObjectResult(responseMessage);
120+
}
121+
}
122+
123+
[Authorize]
124+
internal class Authorize_Attribute_Is_On_Class
125+
{
126+
[FunctionName("Authorize_Attribute_Is_On_Class")]
127+
public IActionResult Run(
128+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
129+
{
130+
var responseMessage = "Hello. This HTTP triggered function is protected.";
131+
132+
return new OkObjectResult(responseMessage);
133+
}
134+
}
135+
136+
[Authorize]
137+
internal class Attribute_Is_On_Class_But_AllowAnonimous_On_Method
138+
{
139+
[AllowAnonymous]
140+
[FunctionName("Attribute_Is_On_Class_But_AllowAnonimous_On_Method")]
141+
public IActionResult Run(
142+
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
143+
{
144+
var responseMessage = "Hello. This HTTP triggered function is protected.";
145+
146+
return new OkObjectResult(responseMessage);
147+
}
148+
}
149+
}
150+
}

src/AzureFunctions.Extensions.OpenIDConnect.sln

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 16
44
VisualStudioVersion = 16.0.30907.101
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctions.Extensions.OpenIDConnect", "AzureFunctions.Extensions.OpenIDConnect/AzureFunctions.Extensions.OpenIDConnect.csproj", "{72379104-48F7-4A31-9946-C36C573D4563}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctions.Extensions.OpenIDConnect", "AzureFunctions.Extensions.OpenIDConnect\AzureFunctions.Extensions.OpenIDConnect.csproj", "{72379104-48F7-4A31-9946-C36C573D4563}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureFunctions.Extensions.OpenIDConnect.Tests", "AzureFunctions.Extensions.OpenIDConnect.Tests\AzureFunctions.Extensions.OpenIDConnect.Tests.csproj", "{09F7B354-FEB9-4A08-8292-234ACD7C679C}"
79
EndProject
810
Global
911
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,6 +17,10 @@ Global
1517
{72379104-48F7-4A31-9946-C36C573D4563}.Debug|Any CPU.Build.0 = Debug|Any CPU
1618
{72379104-48F7-4A31-9946-C36C573D4563}.Release|Any CPU.ActiveCfg = Release|Any CPU
1719
{72379104-48F7-4A31-9946-C36C573D4563}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{09F7B354-FEB9-4A08-8292-234ACD7C679C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{09F7B354-FEB9-4A08-8292-234ACD7C679C}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{09F7B354-FEB9-4A08-8292-234ACD7C679C}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{09F7B354-FEB9-4A08-8292-234ACD7C679C}.Release|Any CPU.Build.0 = Release|Any CPU
1824
EndGlobalSection
1925
GlobalSection(SolutionProperties) = preSolution
2026
HideSolutionNode = FALSE

src/AzureFunctions.Extensions.OpenIDConnect/Configuration/ConfigurationBuilder.cs

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
namespace AzureFunctions.Extensions.OpenIDConnect.Configuration
55
{
6+
using System;
7+
using System.Collections.Generic;
68

79
public class ConfigurationBuilder
810
{
@@ -15,6 +17,9 @@ public class ConfigurationBuilder
1517
internal ConfigurationBuilder(IServiceCollection services)
1618
{
1719
_services = services;
20+
21+
// defaulting
22+
SetTypeCrawler(RouteGuardian.AppDomainTypeCrawler);
1823
}
1924

2025
public void SetTokenValidation(string audience, string issuer)
@@ -43,5 +48,10 @@ private void SetConfigurationManagerSettings(ConfigurationManagerSettings settin
4348
_services.AddSingleton(settings);
4449
_hasConfigurationManagerSettings = true;
4550
}
51+
52+
public void SetTypeCrawler(Func<IEnumerable<Type>> functionTypeCrawler)
53+
{
54+
_services.AddSingleton<FunctionTypeCrawler>(() => functionTypeCrawler());
55+
}
4656
}
4757
}

src/AzureFunctions.Extensions.OpenIDConnect/Configuration/ServicesConfigurationExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static void AddOpenIDConnect(this IServiceCollection services, Action<Con
4444
services.AddSingleton<IJwtSecurityTokenHandlerWrapper, JwtSecurityTokenHandlerWrapper>();
4545
services.AddSingleton<IOpenIdConnectConfigurationManager, OpenIdConnectConfigurationManager>();
4646
services.AddSingleton<IAuthenticationService, AuthenticationService>();
47-
services.AddSingleton<IRouteGuardian, RouteGuardian>();
47+
services.AddSingleton<RouteGuardian>();
4848
services.AddSingleton<IFunctionFilter, AuthorizeFilter>();
4949
}
5050
}

src/AzureFunctions.Extensions.OpenIDConnect/RouteGuardian.cs

+23-17
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,45 @@
88
using Microsoft.AspNetCore.Authorization;
99
using Microsoft.Azure.WebJobs;
1010

11+
12+
public delegate IEnumerable<Type> FunctionTypeCrawler();
13+
1114
public class RouteGuardian : IRouteGuardian
1215
{
13-
1416
private readonly Dictionary<string, AuthorizeAttribute> _routeProtection;
1517

16-
public RouteGuardian()
18+
public static Func<IEnumerable<Type>> AppDomainTypeCrawler = () =>
1719
{
18-
var types = AppDomain.CurrentDomain
19-
.GetAssemblies()
20-
.Where(a => !a.IsDynamic)
21-
.SelectMany(x => x.GetTypes());
20+
return AppDomain.CurrentDomain
21+
.GetAssemblies()
22+
.Where(a => !a.IsDynamic)
23+
.SelectMany(x => x.GetTypes());
24+
};
2225

26+
public RouteGuardian(FunctionTypeCrawler typeCrawler)
27+
{
28+
bool IsAzureFunction(MethodInfo methodInfo) => methodInfo.GetCustomAttributes<FunctionNameAttribute>().Any();
29+
bool IsHttpTrigger(MethodInfo methodInfo) => methodInfo.GetParameters().Any(paramInfo => paramInfo.GetCustomAttributes<HttpTriggerAttribute>().Any());
2330

24-
var httpTriggerMethods = types.SelectMany(type => type.GetMethods()
25-
.Where(
26-
methodInfo => methodInfo.IsPublic && methodInfo.GetCustomAttributes<FunctionNameAttribute>().Any() &&
27-
methodInfo.GetParameters().Any(paramInfo => paramInfo.GetCustomAttributes<HttpTriggerAttribute>().Any())
28-
)
29-
);
31+
var httpTriggerMethods = typeCrawler().SelectMany(type => type.GetMethods())
32+
.Where(methodInfo => methodInfo.IsPublic && IsAzureFunction(methodInfo) && IsHttpTrigger(methodInfo));
3033

3134
var infos = httpTriggerMethods.Select(methodInfo =>
3235
{
3336
var httpTriggerAttribute = methodInfo.GetParameters()
34-
.SelectMany(paramInfo =>paramInfo.GetCustomAttributes<HttpTriggerAttribute>())
37+
.SelectMany(paramInfo => paramInfo.GetCustomAttributes<HttpTriggerAttribute>())
3538
.First();
36-
37-
var functionNameAttribute = methodInfo.GetCustomAttributes<FunctionNameAttribute>().First();
3839

39-
var authorizeAttribute = methodInfo.GetCustomAttributes<AuthorizeAttribute>().FirstOrDefault();
40+
var functionNameAttribute = methodInfo.GetCustomAttributes<FunctionNameAttribute>().First();
4041

42+
var authorizeAttributeOnType = methodInfo.DeclaringType?.GetCustomAttributes<AuthorizeAttribute>().FirstOrDefault();
43+
var authorizeAttributeOnMethod = methodInfo.GetCustomAttributes<AuthorizeAttribute>().FirstOrDefault();
44+
var anonymousAttributeOnMethod = methodInfo.GetCustomAttributes<AllowAnonymousAttribute>().FirstOrDefault();
45+
4146
return new AzureFunctionInfo
4247
{
4348
FunctionName = functionNameAttribute.Name,
44-
AuthorizeAttribute = authorizeAttribute,
49+
AuthorizeAttribute = anonymousAttributeOnMethod != null ? null : authorizeAttributeOnMethod ?? authorizeAttributeOnType,
4550
Route = httpTriggerAttribute.Route
4651
};
4752
});
@@ -62,4 +67,5 @@ private class AzureFunctionInfo
6267
public string Route { get; set; }
6368
}
6469
}
70+
6571
}

0 commit comments

Comments
 (0)