diff --git a/src/isolated/FunctionsAuthenticationMiddleware.cs b/src/isolated/FunctionsAuthenticationMiddleware.cs new file mode 100644 index 0000000..b472276 --- /dev/null +++ b/src/isolated/FunctionsAuthenticationMiddleware.cs @@ -0,0 +1,86 @@ +// +// Copyright (c) DarkLoop. All rights reserved. +// + +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 +{ + private readonly ILogger _logger; + private readonly IAuthenticationHandlerProvider _authenticationHandlerProvider; + private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; + + /// + /// Initializes a new instance of the class. + /// + /// authentication handler provider. + /// authentication scheme provider. + /// A logger object for diagnostics. + public FunctionsAuthenticationMiddleware( + IAuthenticationHandlerProvider authenticationHandlerProvider, + IAuthenticationSchemeProvider authenticationSchemeProvider, + ILogger logger) + { + Check.NotNull(authenticationHandlerProvider, nameof(authenticationHandlerProvider)); + Check.NotNull(authenticationSchemeProvider, nameof(authenticationSchemeProvider)); + Check.NotNull(logger, nameof(logger)); + + _authenticationHandlerProvider = authenticationHandlerProvider; + _authenticationSchemeProvider = authenticationSchemeProvider; + _logger = logger; + } + + /// + 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(authFeatures); + context.Features.Set(authFeatures); + } + else + { + var allSchemes = (await _authenticationSchemeProvider.GetAllSchemesAsync()).ToList(); + _logger.LogDebug( + IsolatedMessages.AuthenticationFailed, + allSchemes.Count > 0 + ? " for " + string.Join(", ", allSchemes) + : string.Empty); + } + } + + await next(context); + } + +} \ No newline at end of file diff --git a/src/isolated/FunctionsAuthorizationMiddleware.cs b/src/isolated/FunctionsAuthorizationMiddleware.cs index 7490e79..97798ce 100644 --- a/src/isolated/FunctionsAuthorizationMiddleware.cs +++ b/src/isolated/FunctionsAuthorizationMiddleware.cs @@ -2,31 +2,33 @@ // Copyright (c) DarkLoop. All rights reserved. // -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 { /// internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddleware { + private readonly IFunctionsAuthorizationProvider _authorizationProvider; private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; - private readonly IAuthorizationPolicyProvider _policyProvider; - private readonly IPolicyEvaluator _policyEvaluator; private readonly IOptionsMonitor _configOptions; private readonly ILogger _logger; + private readonly IPolicyEvaluator _policyEvaluator; + private readonly IAuthorizationPolicyProvider _policyProvider; + + /// /// Initializes a new instance of the class. @@ -38,12 +40,12 @@ internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddlew /// Functions authorization configure options. /// A logger object for diagnostics. public FunctionsAuthorizationMiddleware( - IFunctionsAuthorizationProvider authorizationProvider, - IFunctionsAuthorizationResultHandler authorizationHandler, - IAuthorizationPolicyProvider policyProvider, - IPolicyEvaluator policyEvaluator, - IOptionsMonitor configOptions, - ILogger logger) + IFunctionsAuthorizationProvider authorizationProvider, + IFunctionsAuthorizationResultHandler authorizationHandler, + IAuthorizationPolicyProvider policyProvider, + IPolicyEvaluator policyEvaluator, + IOptionsMonitor configOptions, + ILogger logger) { Check.NotNull(authorizationProvider, nameof(authorizationProvider)); Check.NotNull(authorizationHandler, nameof(authorizationHandler)); @@ -60,6 +62,8 @@ public FunctionsAuthorizationMiddleware( _logger = logger; } + + /// public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { @@ -83,28 +87,10 @@ 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(); - // We also make the features available in the FunctionContext - context.Features.Set(authenticateFeature); - context.Features.Set(authenticateFeature); - - if (filter.AllowAnonymous) - { - await next(context); - return; - } - - if (authenticateResult is not null && !authenticateResult.Succeeded) - { - _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( @@ -112,5 +98,6 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next await _authorizationResultHandler.HandleResultAsync(authContext, httpContext, async (ctx) => await next(ctx)); } + } -} +} \ No newline at end of file diff --git a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs index ec20163..b2ae536 100644 --- a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs +++ b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs @@ -3,6 +3,7 @@ // using DarkLoop.Azure.Functions.Authorization; +using DarkLoop.Azure.Functions.Authorization.Extensions; using DarkLoop.Azure.Functions.Authorization.Features; using Microsoft.Extensions.Hosting; @@ -13,13 +14,45 @@ namespace Microsoft.Azure.Functions.Worker /// public static class FunctionsAuthorizationWorkerAppBuilderExtensions { + + /// + /// Adds both DarkLoop's Functions authentication and authorization middleware to the application pipeline. + /// + /// The current builder. + public static IFunctionsWorkerApplicationBuilder UseFunctionsAuth(this IFunctionsWorkerApplicationBuilder builder) + { + builder.UseWhen(context => + context.IsHttpTrigger() && + context.Features.Get() is not null); + + builder.UseWhen(context => + context.IsHttpTrigger() && + context.Features.Get() is not null); + + return builder; + } + + /// + /// Adds DarkLoop's Functions authentication middleware to the application pipeline. + /// if the function is an HTTP trigger and the is available. + /// + /// The current builder. + public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthentication(this IFunctionsWorkerApplicationBuilder builder) + { + return builder.UseWhen(context => + context.IsHttpTrigger() && + context.Features.Get() is not null); + } + /// /// Adds DarkLoop's Functions authorization middleware to the application pipeline. + /// if the function is an HTTP trigger and the is available. /// /// The current builder. public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthorization(this IFunctionsWorkerApplicationBuilder builder) { return builder.UseWhen(context => + context.IsHttpTrigger() && context.Features.Get() is not null); } }