Skip to content

Commit eff3b84

Browse files
helgeuHelge René Urholm
and
Helge René Urholm
authored
Add a fsharp test project to ease testing (#74)
* Add a fsharp test project to ease testing --------- Co-authored-by: Helge René Urholm <[email protected]>
1 parent dbfa2a5 commit eff3b84

11 files changed

+247
-0
lines changed

Functions-Authorize.sln

+7
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Isolated.Tests", "test\Isol
5353
EndProject
5454
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InProc.Tests", "test\InProc.Tests\InProc.Tests.csproj", "{3E24DA59-3292-430E-B784-5FC13CAEDA5E}"
5555
EndProject
56+
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SampleIsolatedFunctionsFSharp_V4", "sample\SampleIsolatedFunctionsFSharp.V4\SampleIsolatedFunctionsFSharp_V4.fsproj", "{60017B6F-8423-4D2F-A080-9C7A40C663C3}"
57+
EndProject
5658
Global
5759
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5860
Debug|Any CPU = Debug|Any CPU
@@ -95,6 +97,10 @@ Global
9597
{3E24DA59-3292-430E-B784-5FC13CAEDA5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
9698
{3E24DA59-3292-430E-B784-5FC13CAEDA5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
9799
{3E24DA59-3292-430E-B784-5FC13CAEDA5E}.Release|Any CPU.Build.0 = Release|Any CPU
100+
{60017B6F-8423-4D2F-A080-9C7A40C663C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
101+
{60017B6F-8423-4D2F-A080-9C7A40C663C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
102+
{60017B6F-8423-4D2F-A080-9C7A40C663C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
103+
{60017B6F-8423-4D2F-A080-9C7A40C663C3}.Release|Any CPU.Build.0 = Release|Any CPU
98104
EndGlobalSection
99105
GlobalSection(SolutionProperties) = preSolution
100106
HideSolutionNode = FALSE
@@ -109,6 +115,7 @@ Global
109115
{E7143566-8DA8-4D2F-AA23-7FB0BADE229F} = {051D16F8-54BB-482B-B2A9-47E2DA89E6DB}
110116
{4E74E715-7549-46C4-AF0F-464C24FA00E7} = {051D16F8-54BB-482B-B2A9-47E2DA89E6DB}
111117
{3E24DA59-3292-430E-B784-5FC13CAEDA5E} = {051D16F8-54BB-482B-B2A9-47E2DA89E6DB}
118+
{60017B6F-8423-4D2F-A080-9C7A40C663C3} = {53EC585B-CE9B-4E7D-B2E2-F7A9B6DA0FE7}
112119
EndGlobalSection
113120
GlobalSection(ExtensibilityGlobals) = postSolution
114121
SolutionGuid = {546E3A6C-060C-4630-BEEE-46A1F8715347}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"recommendations": [
3+
"ms-azuretools.vscode-azurefunctions"
4+
]
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace SampleInProcFunctions.V4
2+
3+
open System.Security.Claims
4+
open Common.Tests
5+
open Microsoft.AspNetCore.Http
6+
open Microsoft.AspNetCore.Mvc
7+
open Microsoft.Azure.Functions.Worker
8+
open Microsoft.Extensions.Logging
9+
open System
10+
11+
type HelperFunctions() =
12+
13+
[<Function("GetTestToken")>]
14+
member _.Run(
15+
[<HttpTrigger("get", Route = null)>]
16+
req: HttpRequest,
17+
log: ILogger) =
18+
task {
19+
20+
let firstName = "Test"
21+
let lastName = "User"
22+
let email = "[email protected]"
23+
let token = JwtUtils.GenerateJwtToken(
24+
[
25+
new Claim("aud", "api://default")
26+
new Claim("iss", "https://localhost/jwt/")
27+
new Claim("scp", "user_impersonation")
28+
new Claim("tid", Guid.NewGuid().ToString())
29+
new Claim("oid", Guid.NewGuid().ToString())
30+
new Claim("name", $"{firstName} {lastName}")
31+
new Claim(ClaimTypes.Name, email)
32+
new Claim(ClaimTypes.Upn, email)
33+
new Claim(ClaimTypes.Email, email)
34+
new Claim(ClaimTypes.GivenName, firstName)
35+
new Claim(ClaimTypes.Surname, lastName)
36+
new Claim("role", "Just a user")
37+
new Claim("role", "admin")
38+
])
39+
40+
return OkObjectResult(token)
41+
}
42+
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
module Program
2+
3+
open Common.Tests
4+
open DarkLoop.Azure.Functions.Authorization
5+
open Microsoft.Azure.Functions.Worker
6+
open Microsoft.Extensions.DependencyInjection
7+
open Microsoft.Extensions.Hosting
8+
open Microsoft.IdentityModel.Tokens
9+
10+
// IMPORTANT: because local.settings.json is not included in the repository, you must create it manually
11+
// If you don't create it. the isolated function will not run. Ensure that the file has the following content:
12+
//
13+
// {
14+
// "IsEncrypted": false,
15+
// "Values": {
16+
// "AzureWebJobsStorage": "UseDevelopmentStorage=true",
17+
// "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
18+
// }
19+
// }
20+
21+
let host =
22+
HostBuilder()
23+
.ConfigureFunctionsWebApplication(fun builder ->
24+
//This is needed to make F# variants of startup work nicely
25+
FunctionsAuthorizationExtensionStartup().Configure(builder)
26+
builder.UseFunctionsAuthorization() |> ignore )
27+
.ConfigureServices(fun services ->
28+
services
29+
.AddFunctionsAuthentication(JwtFunctionsBearerDefaults.AuthenticationScheme)
30+
.AddJwtFunctionsBearer(fun options ->
31+
// this line is here to bypass the token validation
32+
// and test the functionality of this library.
33+
// you can create a dummy token by executing the GetTestToken function in HelperFunctions.cs
34+
// THE FOLLOWING LINE SHOULD BE REMOVED IN A REAL-WORLD SCENARIO
35+
options.SecurityTokenValidators.Add(TestTokenValidator())
36+
37+
// this is what you should look for in a real-world scenario
38+
// comment the lines if you cloned this repository and want to test the library
39+
options.Authority <- "https://login.microsoftonline.com/<your-tenant>"
40+
options.Audience <- "<your-audience>"
41+
options.TokenValidationParameters <- TokenValidationParameters
42+
(
43+
ValidateIssuer = true,
44+
ValidateAudience = true,
45+
ValidateLifetime = true,
46+
ValidateIssuerSigningKey = true
47+
)
48+
()
49+
) |> ignore
50+
51+
services
52+
.AddFunctionsAuthorization(fun options ->
53+
// Add your policies here
54+
()
55+
) |> ignore
56+
)
57+
.Build()
58+
59+
host.Run()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"dependencies": {
3+
"appInsights1": {
4+
"type": "appInsights"
5+
},
6+
"storage1": {
7+
"type": "storage",
8+
"connectionId": "AzureWebJobsStorage"
9+
}
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"dependencies": {
3+
"appInsights1": {
4+
"type": "appInsights.sdk"
5+
},
6+
"storage1": {
7+
"type": "storage.emulator",
8+
"connectionId": "AzureWebJobsStorage"
9+
}
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
5+
<OutputType>Exe</OutputType>
6+
<UserSecretsId>17c2def3-36ba-461c-8cf2-2305557bb98b</UserSecretsId>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<Compile Include="HelperFunctions.fs" />
10+
<Compile Include="TestFunction.fs" />
11+
</ItemGroup>
12+
<ItemGroup>
13+
<Content Include="host.json" />
14+
<Content Include="local.settings.json" />
15+
<Compile Include="Program.fs" />
16+
</ItemGroup>
17+
<ItemGroup>
18+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
19+
<PackageReference Include="Azure.Data.Tables" Version="12.9.1" />
20+
<PackageReference Include="Azure.Identity" Version="1.13.0" />
21+
<PackageReference Include="Azure.Storage.Blobs" Version="12.22.2" />
22+
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.20.1" />
23+
<PackageReference Include="Azure.Storage.Queues" Version="12.20.1" />
24+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.1.0" />
25+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.16.4" />
26+
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
27+
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.1.0" />
28+
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.7.6" />
29+
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.1" />
30+
</ItemGroup>
31+
<ItemGroup>
32+
<ProjectReference Include="..\..\src\isolated\DarkLoop.Azure.Functions.Authorization.Isolated.csproj" />
33+
<ProjectReference Include="..\..\test\Common.Tests\Common.Tests.csproj" />
34+
</ItemGroup>
35+
<ItemGroup>
36+
<None Update="host.json">
37+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
38+
</None>
39+
<None Update="local.settings.json">
40+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
41+
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
42+
</None>
43+
</ItemGroup>
44+
<ItemGroup>
45+
<Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
46+
</ItemGroup>
47+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace SampleIsolatedFunctionsFSharp.V4
2+
3+
open System.Text
4+
open DarkLoop.Azure.Functions.Authorization
5+
open Microsoft.AspNetCore.Authentication
6+
open Microsoft.AspNetCore.Authorization
7+
open Microsoft.AspNetCore.Http
8+
open Microsoft.AspNetCore.Mvc
9+
open Microsoft.Azure.Functions.Worker
10+
open Microsoft.Extensions.DependencyInjection
11+
open Microsoft.Extensions.Logging
12+
13+
14+
[<FunctionAuthorize(AuthenticationSchemes = "FunctionsBearer")>]
15+
type TestFunction(logger:ILogger<TestFunction>) =
16+
let _logger = logger
17+
18+
[<Function("TestFunction")>]
19+
[<Authorize(Roles = "admin")>]
20+
member _.Run([<HttpTrigger("get", "post")>] req:HttpRequest) =
21+
task {
22+
_logger.LogInformation("F# HTTP trigger function processed a request.")
23+
24+
let provider = req.HttpContext.RequestServices
25+
let schProvider = provider.GetService<IAuthenticationSchemeProvider>()
26+
27+
let sb = new StringBuilder()
28+
sb.AppendLine("Authentication schemes:") |> ignore
29+
30+
if (schProvider <> null) then
31+
let! allScheme = schProvider.GetAllSchemesAsync()
32+
for scheme in allScheme do
33+
sb.AppendLine($" {scheme.Name} -> {scheme.HandlerType}") |> ignore
34+
35+
36+
sb.AppendLine()|> ignore
37+
sb.AppendLine($"User:")|> ignore
38+
sb.AppendLine($" Name -> {req.HttpContext.User.Identity.Name}")|> ignore
39+
let email = req.HttpContext.User.FindFirst("email")|> Option.ofObj|>Option.map _.Value
40+
sb.AppendLine($" Email -> {email}")|> ignore
41+
42+
return OkObjectResult(sb.ToString())
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"version": "2.0",
3+
"logging": {
4+
"applicationInsights": {
5+
"samplingSettings": {
6+
"isEnabled": true,
7+
"excludedTypes": "Request"
8+
},
9+
"enableLiveMetricsFilters": true
10+
}
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"IsEncrypted": false,
3+
"Values": {
4+
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
5+
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
6+
}
7+
}

src/isolated/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ adds the `"FunctionsBearer"` scheme. Clients still submit token for Authorizatio
6767
Notice the call to `UseFunctionsAuthorization` in the `ConfigureFunctionsWebAppliction` method.
6868
This is required to ensure that the middleware is placed in the pipeline where required function information is available.`
6969

70+
Mind that the startup if coding in F# will be somewhat different. Please do check the [sample for F#](../../sample/SampleIsolatedFunctionsFSharp.V4/Program.fs)
71+
7072
### Using the attribute
7173
And now lets use `FunctionAuthorizeAttribute` the same way we use `AuthorizeAttribute` in our ASP.NET Core applications.
7274
```csharp

0 commit comments

Comments
 (0)