Skip to content
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
102 changes: 102 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,102 @@
// <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
{
internal sealed class FunctionsAuthenticationMiddleware : IFunctionsWorkerMiddleware
{
#region Private Fields

private readonly IAuthenticationHandlerProvider _authenticationHandlerProvider;
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
private readonly ILogger<FunctionsAuthenticationMiddleware> _logger;

#endregion Private Fields

#region Public Constructors

/// <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;
}

#endregion Public Constructors

#region Public Methods

/// <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);
}

#endregion Public Methods
}
}
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,
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