Giter VIP home page Giter VIP logo

Comments (3)

SeanFeldman avatar SeanFeldman commented on July 21, 2024 1

It's not straightforward forward, but you could use reflection to get access to the attribute on the invoked function.

file static class FunctionContextExtensions
{
    public static MethodInfo GetTargetFunctionMethod(this FunctionContext context)
    {
        var entryPoint = context.FunctionDefinition.EntryPoint;
        var assemblyPath = context.FunctionDefinition.PathToAssembly;

        var assembly = Assembly.LoadFrom(assemblyPath);
        var typeName = entryPoint[..entryPoint.LastIndexOf('.')];
        var type = assembly.GetType(typeName);
        var methodName = entryPoint[(entryPoint.LastIndexOf('.') + 1)..];
        var method = type?.GetMethod(methodName);

        return method ?? throw new Exception("Could not get target function from the context");
    }
}

from azure-functions-dotnet-worker.

fabiocav avatar fabiocav commented on July 21, 2024 1

We have a set of APIs that were recently introduced that may help here.

We'll flag this for review and provide more information when that is available.

from azure-functions-dotnet-worker.

davidpeden3 avatar davidpeden3 commented on July 21, 2024 1

@stevendarby, similar to @SeanFeldman's suggestion, i also use reflection. however, i do it differently. i highly recommend that you separate out the reflection code from the run-time execution of your function. you can also cache (or, in my case, load the results as a singleton in a container) and initialize the attribute analysis at app start time (or lazily on first function hit).

i use this technique to manage authentication (jwt validation) and authorization (scope validation).

public class FunctionAuthRules
{
    private readonly HashSet<string> _anonymousFunctions = new(){"RenderSwaggerUI","RenderSwaggerDocument"};
    private readonly Dictionary<string, string> _scopedFunctions = new();

    public FunctionAuthRules() : this(GetAppDomainMethods())
    {
    }

    protected FunctionAuthRules(IEnumerable<MethodInfo> methods)
    {
        foreach (var method in methods)
        {
            var functionName = method.GetCustomAttribute<FunctionAttribute>()?.Name;
     
            if (functionName is null)
            {
                continue;
            }

            if (method.GetCustomAttribute<AllowAnonymousAccessAttribute>() is not null)
            {
                _anonymousFunctions.Add(functionName);
            }

            if (method.GetCustomAttribute<RequireScopeAttribute>() is { } scoped)
            {
                _scopedFunctions.Add(functionName, scoped.RequiredScope); 
            }
        }
    }

    public bool AllowAnonymousAccess(FunctionContext context)
    {
        return _anonymousFunctions.Contains(context.FunctionDefinition.Name);
    }

    public string? RequiredScope(FunctionContext context)
    {
        var hasScope = _scopedFunctions.TryGetValue(context.FunctionDefinition.Name, out var scope);

        return hasScope ? scope : null;
    }

    private static IEnumerable<MethodInfo> GetAppDomainMethods()
    {
        return AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(a => a.GetTypes())
            .SelectMany(t => t.GetMethods());
    }
}

here is the scope attribute:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class RequireScopeAttribute : Attribute
{
    public string RequiredScope { get; }

    public RequireScopeAttribute(string requiredScope)
    {
        RequiredScope = requiredScope;
    }
}

then you can leverage your "rules" in a middleware:

public class JwtBearerAuthenticationMiddleware : IFunctionsWorkerMiddleware
{
    private readonly IJwtValidationService _jwtValidationService;
    private readonly FunctionAuthRules _rules;
    private readonly ILogger _logger;

    public JwtBearerAuthenticationMiddleware(IJwtValidationService jwtValidationService, FunctionAuthRules rules, ILogger logger)
    {
        _jwtValidationService = jwtValidationService;
        _rules = rules;
        _logger = logger;
    }

    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        var requestData = await context.GetHttpRequestDataAsync();

        if (requestData is null)
        {
            throw new InvalidOperationException("Unable to get request data");
        }

        if (_rules.AllowAnonymousAccess(context))
        {
            await next(context);

            return;
        }

        if (!requestData.TryGetJwt(out var jwt))
        {
            context.GetInvocationResult().Value = requestData.CreateResponse(HttpStatusCode.Unauthorized);
            _logger.Warning("Authorization header not found");

            return;
        }

        var jwtValidationResponse = await _jwtValidationService.ValidateJwt(jwt!);

        if (!jwtValidationResponse.IsValid)
        {
            context.GetInvocationResult().Value = requestData.CreateResponse(HttpStatusCode.Unauthorized);
            _logger.Warning("Unauthorized: {Request_Uri}", requestData.Url);

            return;
        }

        context.SetApiClaimsPrincipal(new ApiClaimsPrincipal(jwtValidationResponse.ToUserPrincipal()));

        await next(context);
    }
}

and

public class RequireScopeMiddleware : IFunctionsWorkerMiddleware
{
    private readonly FunctionAuthRules _rules;
    private readonly ILogger _logger;

    public RequireScopeMiddleware(FunctionAuthRules rules, ILogger logger)
    {
        _rules = rules;
        _logger = logger;
    }

    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        var requestData = await context.GetHttpRequestDataAsync();

        if (requestData is null)
        {
            throw new InvalidOperationException("Unable to get request data");
        }

        if (_rules.RequiredScope(context) is { } requiredScope)
        {
            var principal = context.GetApiClaimsPrincipal();

            if (principal is null)
            {
                context.GetInvocationResult().Value = requestData.CreateResponse(HttpStatusCode.Unauthorized);
                _logger.Warning("Unauthorized: {Request_Uri}", requestData.Url);

                return;
            }

            if (!principal.HasScope(requiredScope))
            {
                context.GetInvocationResult().Value = requestData.CreateResponse(HttpStatusCode.Forbidden);
                _logger
                    .ForContext(nameof(principal.ClientId), principal.ClientId)
                    .ForContext(nameof(principal.Scopes), principal.Scopes)
                    .ForContext("RequiredScope", requiredScope)
                    .Warning("Forbidden: {Request_Uri}", requestData.Url);

                return;
            }
        }

        await next(context);
    }
}

from azure-functions-dotnet-worker.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.