Skip to content

split middleware, so ClaimsEnrichment middleware can be implemented #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions src/isolated/FunctionsAuthenticationMiddleware.cs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this to a new project? Darkloop.Azure.Functions.Authentication.Isolated right now we don't have a need for any abstractions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make sure to keep default indentation of the rest of the project. 4 spaces

Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// <copyright file="FunctionsAuthenticationMiddleware.cs" company="DarkLoop" author="Arturo Martinez">
// Copyright (c) DarkLoop. All rights reserved.
// </copyright>

using DarkLoop.Azure.Functions.Authorization.Internal;
using DarkLoop.Azure.Functions.Authorization.Properties;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace DarkLoop.Azure.Functions.Authorization;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please let's stick to a convention for the whole solution. Let's add braces for namespaces, since I don't think we want to go through every file and change to this format.


internal sealed class FunctionsAuthenticationMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<FunctionsAuthenticationMiddleware> _logger;
private readonly IAuthenticationHandlerProvider _authenticationHandlerProvider;
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;

/// <summary>
/// Initializes a new instance of the <see cref="FunctionsAuthenticationMiddleware"/> class.
/// </summary>
/// <param name="authenticationHandlerProvider">authentication handler provider.</param>
/// <param name="authenticationSchemeProvider">authentication scheme provider.</param>
/// <param name="logger">A logger object for diagnostics.</param>
public FunctionsAuthenticationMiddleware(
IAuthenticationHandlerProvider authenticationHandlerProvider,
IAuthenticationSchemeProvider authenticationSchemeProvider,
ILogger<FunctionsAuthenticationMiddleware> logger)
{
Check.NotNull(authenticationHandlerProvider, nameof(authenticationHandlerProvider));
Check.NotNull(authenticationSchemeProvider, nameof(authenticationSchemeProvider));
Check.NotNull(logger, nameof(logger));

_authenticationHandlerProvider = authenticationHandlerProvider;
_authenticationSchemeProvider = authenticationSchemeProvider;
_logger = logger;
}

/// <inheritdoc />
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode);

foreach (var scheme in await _authenticationSchemeProvider.GetRequestHandlerSchemesAsync())
{
var handler = await _authenticationHandlerProvider.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
return;
}
}

var defaultAuthenticate = await _authenticationSchemeProvider.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
{
httpContext.User = result.Principal;
}
if (result?.Succeeded ?? false)
{
var authFeatures = httpContext.Features.SetAuthenticationFeatures(result);
context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
context.Features.Set<IAuthenticateResultFeature>(authFeatures);
}
else
{
var allSchemes = (await _authenticationSchemeProvider.GetAllSchemesAsync()).ToList();
_logger.LogDebug(
IsolatedMessages.AuthenticationFailed,
allSchemes.Count > 0
? " for " + string.Join(", ", allSchemes)
: string.Empty);
}
}

await next(context);
}

}
53 changes: 20 additions & 33 deletions src/isolated/FunctionsAuthorizationMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,33 @@
// Copyright (c) DarkLoop. All rights reserved.
// </copyright>

using System;
using System.Threading.Tasks;
using DarkLoop.Azure.Functions.Authorization.Internal;
using DarkLoop.Azure.Functions.Authorization.Properties;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;

namespace DarkLoop.Azure.Functions.Authorization
{
/// <inheritdoc cref="IFunctionsWorkerMiddleware"/>
internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddleware
{

private readonly IFunctionsAuthorizationProvider _authorizationProvider;
private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler;
private readonly IAuthorizationPolicyProvider _policyProvider;
private readonly IPolicyEvaluator _policyEvaluator;
private readonly IOptionsMonitor<FunctionsAuthorizationOptions> _configOptions;
private readonly ILogger<FunctionsAuthorizationMiddleware> _logger;
private readonly IPolicyEvaluator _policyEvaluator;
private readonly IAuthorizationPolicyProvider _policyProvider;



/// <summary>
/// Initializes a new instance of the <see cref="FunctionsAuthorizationMiddleware"/> class.
Expand All @@ -38,12 +40,12 @@ internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddlew
/// <param name="configOptions">Functions authorization configure options.</param>
/// <param name="logger">A logger object for diagnostics.</param>
public FunctionsAuthorizationMiddleware(
IFunctionsAuthorizationProvider authorizationProvider,
IFunctionsAuthorizationResultHandler authorizationHandler,
IAuthorizationPolicyProvider policyProvider,
IPolicyEvaluator policyEvaluator,
IOptionsMonitor<FunctionsAuthorizationOptions> configOptions,
ILogger<FunctionsAuthorizationMiddleware> logger)
IFunctionsAuthorizationProvider authorizationProvider,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's reset indentation on these parameters

IFunctionsAuthorizationResultHandler authorizationHandler,
IAuthorizationPolicyProvider policyProvider,
IPolicyEvaluator policyEvaluator,
IOptionsMonitor<FunctionsAuthorizationOptions> configOptions,
ILogger<FunctionsAuthorizationMiddleware> logger)
{
Check.NotNull(authorizationProvider, nameof(authorizationProvider));
Check.NotNull(authorizationHandler, nameof(authorizationHandler));
Expand All @@ -60,6 +62,8 @@ public FunctionsAuthorizationMiddleware(
_logger = logger;
}



/// <inheritdoc />
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
Expand All @@ -83,34 +87,17 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next
return;
}

var authenticateResult = await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext);

var authenticateFeature = httpContext.Features.SetAuthenticationFeatures(authenticateResult);
var authenticateFeature = context.Features.Get<IAuthenticateResultFeature>();

// We also make the features available in the FunctionContext
context.Features.Set<IAuthenticateResultFeature>(authenticateFeature);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If user does not configure authentication middleware the feature needs to be set. We don't want to break this functionality. This is the same way it happens in ASPNET Core

context.Features.Set<IHttpAuthenticationFeature>(authenticateFeature);

if (filter.AllowAnonymous)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this check removed? We still need to allow for execution of an action regardless of authentication result if it's marked with AllowAnonymous

{
await next(context);
return;
}

if (authenticateResult is not null && !authenticateResult.Succeeded)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this section shouldn't be removed either

{
_logger.LogDebug(
IsolatedMessages.AuthenticationFailed,
filter.Policy.AuthenticationSchemes.Count > 0
? " for " + string.Join(", ", filter.Policy.AuthenticationSchemes)
: string.Empty);
}
var authenticateResult = authenticateFeature?.AuthenticateResult ??
await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext);

var authorizeResult = await _policyEvaluator.AuthorizeAsync(filter.Policy, authenticateResult!, httpContext, httpContext);
var authContext = new FunctionAuthorizationContext<FunctionContext>(
context.FunctionDefinition.Name, context, filter.Policy, authorizeResult);

await _authorizationResultHandler.HandleResultAsync(authContext, httpContext, async (ctx) => await next(ctx));
}

}
}
}
33 changes: 33 additions & 0 deletions src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should separate extension methods, each one in their own assembly

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// </copyright>

using DarkLoop.Azure.Functions.Authorization;
using DarkLoop.Azure.Functions.Authorization.Extensions;
using DarkLoop.Azure.Functions.Authorization.Features;
using Microsoft.Extensions.Hosting;

Expand All @@ -13,13 +14,45 @@ namespace Microsoft.Azure.Functions.Worker
/// </summary>
public static class FunctionsAuthorizationWorkerAppBuilderExtensions
{

/// <summary>
/// Adds both DarkLoop's Functions authentication and authorization middleware to the application pipeline.
/// </summary>
/// <param name="builder">The current builder.</param>
public static IFunctionsWorkerApplicationBuilder UseFunctionsAuth(this IFunctionsWorkerApplicationBuilder builder)
{
builder.UseWhen<FunctionsAuthenticationMiddleware>(context =>
context.IsHttpTrigger() &&
context.Features.Get<IFunctionsAuthorizationFeature>() is not null);

builder.UseWhen<FunctionsAuthorizationMiddleware>(context =>
context.IsHttpTrigger() &&
context.Features.Get<IFunctionsAuthorizationFeature>() is not null);

return builder;
}

/// <summary>
/// Adds DarkLoop's Functions authentication middleware to the application pipeline.
/// if the function is an HTTP trigger and the <see cref="IFunctionsAuthorizationFeature"/> is available.
/// </summary>
/// <param name="builder">The current builder.</param>
public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthentication(this IFunctionsWorkerApplicationBuilder builder)
{
return builder.UseWhen<FunctionsAuthenticationMiddleware>(context =>
context.IsHttpTrigger() &&
context.Features.Get<IFunctionsAuthorizationFeature>() is not null);
}

/// <summary>
/// Adds DarkLoop's Functions authorization middleware to the application pipeline.
/// if the function is an HTTP trigger and the <see cref="IFunctionsAuthorizationFeature"/> is available.
/// </summary>
/// <param name="builder">The current builder.</param>
public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthorization(this IFunctionsWorkerApplicationBuilder builder)
{
return builder.UseWhen<FunctionsAuthorizationMiddleware>(context =>
context.IsHttpTrigger() &&
context.Features.Get<IFunctionsAuthorizationFeature>() is not null);
}
}
Expand Down
Loading