Skip to content

Commit

Permalink
Add a fsharp test project to ease testing (#74)
Browse files Browse the repository at this point in the history
* Add a fsharp test project to ease testing
---------

Co-authored-by: Helge René Urholm <[email protected]>
  • Loading branch information
helgeu and Helge René Urholm authored Dec 9, 2024
1 parent dbfa2a5 commit eff3b84
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Functions-Authorize.sln
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Isolated.Tests", "test\Isol
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InProc.Tests", "test\InProc.Tests\InProc.Tests.csproj", "{3E24DA59-3292-430E-B784-5FC13CAEDA5E}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SampleIsolatedFunctionsFSharp_V4", "sample\SampleIsolatedFunctionsFSharp.V4\SampleIsolatedFunctionsFSharp_V4.fsproj", "{60017B6F-8423-4D2F-A080-9C7A40C663C3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -95,6 +97,10 @@ Global
{3E24DA59-3292-430E-B784-5FC13CAEDA5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E24DA59-3292-430E-B784-5FC13CAEDA5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E24DA59-3292-430E-B784-5FC13CAEDA5E}.Release|Any CPU.Build.0 = Release|Any CPU
{60017B6F-8423-4D2F-A080-9C7A40C663C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{60017B6F-8423-4D2F-A080-9C7A40C663C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{60017B6F-8423-4D2F-A080-9C7A40C663C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{60017B6F-8423-4D2F-A080-9C7A40C663C3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -109,6 +115,7 @@ Global
{E7143566-8DA8-4D2F-AA23-7FB0BADE229F} = {051D16F8-54BB-482B-B2A9-47E2DA89E6DB}
{4E74E715-7549-46C4-AF0F-464C24FA00E7} = {051D16F8-54BB-482B-B2A9-47E2DA89E6DB}
{3E24DA59-3292-430E-B784-5FC13CAEDA5E} = {051D16F8-54BB-482B-B2A9-47E2DA89E6DB}
{60017B6F-8423-4D2F-A080-9C7A40C663C3} = {53EC585B-CE9B-4E7D-B2E2-F7A9B6DA0FE7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {546E3A6C-060C-4630-BEEE-46A1F8715347}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"ms-azuretools.vscode-azurefunctions"
]
}
43 changes: 43 additions & 0 deletions sample/SampleIsolatedFunctionsFSharp.V4/HelperFunctions.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace SampleInProcFunctions.V4

open System.Security.Claims
open Common.Tests
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Mvc
open Microsoft.Azure.Functions.Worker
open Microsoft.Extensions.Logging
open System

type HelperFunctions() =

[<Function("GetTestToken")>]
member _.Run(
[<HttpTrigger("get", Route = null)>]
req: HttpRequest,
log: ILogger) =
task {

let firstName = "Test"
let lastName = "User"
let email = "[email protected]"
let token = JwtUtils.GenerateJwtToken(
[
new Claim("aud", "api://default")
new Claim("iss", "https://localhost/jwt/")
new Claim("scp", "user_impersonation")
new Claim("tid", Guid.NewGuid().ToString())
new Claim("oid", Guid.NewGuid().ToString())
new Claim("name", $"{firstName} {lastName}")
new Claim(ClaimTypes.Name, email)
new Claim(ClaimTypes.Upn, email)
new Claim(ClaimTypes.Email, email)
new Claim(ClaimTypes.GivenName, firstName)
new Claim(ClaimTypes.Surname, lastName)
new Claim("role", "Just a user")
new Claim("role", "admin")
])

return OkObjectResult(token)
}


59 changes: 59 additions & 0 deletions sample/SampleIsolatedFunctionsFSharp.V4/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module Program

open Common.Tests
open DarkLoop.Azure.Functions.Authorization
open Microsoft.Azure.Functions.Worker
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Hosting
open Microsoft.IdentityModel.Tokens

// IMPORTANT: because local.settings.json is not included in the repository, you must create it manually
// If you don't create it. the isolated function will not run. Ensure that the file has the following content:
//
// {
// "IsEncrypted": false,
// "Values": {
// "AzureWebJobsStorage": "UseDevelopmentStorage=true",
// "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
// }
// }

let host =
HostBuilder()
.ConfigureFunctionsWebApplication(fun builder ->
//This is needed to make F# variants of startup work nicely
FunctionsAuthorizationExtensionStartup().Configure(builder)
builder.UseFunctionsAuthorization() |> ignore )
.ConfigureServices(fun services ->
services
.AddFunctionsAuthentication(JwtFunctionsBearerDefaults.AuthenticationScheme)
.AddJwtFunctionsBearer(fun options ->
// this line is here to bypass the token validation
// and test the functionality of this library.
// you can create a dummy token by executing the GetTestToken function in HelperFunctions.cs
// THE FOLLOWING LINE SHOULD BE REMOVED IN A REAL-WORLD SCENARIO
options.SecurityTokenValidators.Add(TestTokenValidator())

// this is what you should look for in a real-world scenario
// comment the lines if you cloned this repository and want to test the library
options.Authority <- "https://login.microsoftonline.com/<your-tenant>"
options.Audience <- "<your-audience>"
options.TokenValidationParameters <- TokenValidationParameters
(
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true
)
()
) |> ignore

services
.AddFunctionsAuthorization(fun options ->
// Add your policies here
()
) |> ignore
)
.Build()

host.Run()
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"dependencies": {
"appInsights1": {
"type": "appInsights"
},
"storage1": {
"type": "storage",
"connectionId": "AzureWebJobsStorage"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"dependencies": {
"appInsights1": {
"type": "appInsights.sdk"
},
"storage1": {
"type": "storage.emulator",
"connectionId": "AzureWebJobsStorage"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<UserSecretsId>17c2def3-36ba-461c-8cf2-2305557bb98b</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<Compile Include="HelperFunctions.fs" />
<Compile Include="TestFunction.fs" />
</ItemGroup>
<ItemGroup>
<Content Include="host.json" />
<Content Include="local.settings.json" />
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Azure.Data.Tables" Version="12.9.1" />
<PackageReference Include="Azure.Identity" Version="1.13.0" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.22.2" />
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.20.1" />
<PackageReference Include="Azure.Storage.Queues" Version="12.20.1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.1.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.16.4" />
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.7.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\isolated\DarkLoop.Azure.Functions.Authorization.Isolated.csproj" />
<ProjectReference Include="..\..\test\Common.Tests\Common.Tests.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
</ItemGroup>
</Project>
43 changes: 43 additions & 0 deletions sample/SampleIsolatedFunctionsFSharp.V4/TestFunction.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace SampleIsolatedFunctionsFSharp.V4

open System.Text
open DarkLoop.Azure.Functions.Authorization
open Microsoft.AspNetCore.Authentication
open Microsoft.AspNetCore.Authorization
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Mvc
open Microsoft.Azure.Functions.Worker
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Logging


[<FunctionAuthorize(AuthenticationSchemes = "FunctionsBearer")>]
type TestFunction(logger:ILogger<TestFunction>) =
let _logger = logger

[<Function("TestFunction")>]
[<Authorize(Roles = "admin")>]
member _.Run([<HttpTrigger("get", "post")>] req:HttpRequest) =
task {
_logger.LogInformation("F# HTTP trigger function processed a request.")

let provider = req.HttpContext.RequestServices
let schProvider = provider.GetService<IAuthenticationSchemeProvider>()

let sb = new StringBuilder()
sb.AppendLine("Authentication schemes:") |> ignore

if (schProvider <> null) then
let! allScheme = schProvider.GetAllSchemesAsync()
for scheme in allScheme do
sb.AppendLine($" {scheme.Name} -> {scheme.HandlerType}") |> ignore


sb.AppendLine()|> ignore
sb.AppendLine($"User:")|> ignore
sb.AppendLine($" Name -> {req.HttpContext.User.Identity.Name}")|> ignore
let email = req.HttpContext.User.FindFirst("email")|> Option.ofObj|>Option.map _.Value
sb.AppendLine($" Email -> {email}")|> ignore

return OkObjectResult(sb.ToString())
}
12 changes: 12 additions & 0 deletions sample/SampleIsolatedFunctionsFSharp.V4/host.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
},
"enableLiveMetricsFilters": true
}
}
}
7 changes: 7 additions & 0 deletions sample/SampleIsolatedFunctionsFSharp.V4/local.settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
}
}
2 changes: 2 additions & 0 deletions src/isolated/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ adds the `"FunctionsBearer"` scheme. Clients still submit token for Authorizatio
Notice the call to `UseFunctionsAuthorization` in the `ConfigureFunctionsWebAppliction` method.
This is required to ensure that the middleware is placed in the pipeline where required function information is available.`

Mind that the startup if coding in F# will be somewhat different. Please do check the [sample for F#](../../sample/SampleIsolatedFunctionsFSharp.V4/Program.fs)

### Using the attribute
And now lets use `FunctionAuthorizeAttribute` the same way we use `AuthorizeAttribute` in our ASP.NET Core applications.
```csharp
Expand Down

0 comments on commit eff3b84

Please sign in to comment.