private static Dictionary <string, object> GetFunctionArguments(FunctionDescriptor function, HttpRequestMessage request)
        {
            ParameterDescriptor         triggerParameter = function.Parameters.First(p => p.IsTrigger);
            Dictionary <string, object> arguments        = new Dictionary <string, object>();

            if (triggerParameter.Type != typeof(HttpRequestMessage))
            {
                HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => string.Compare("HttpTrigger", p.Type, StringComparison.OrdinalIgnoreCase) == 0);
                if (!string.IsNullOrEmpty(httpFunctionMetadata.WebHookType))
                {
                    WebHookHandlerContext webHookContext;
                    if (request.Properties.TryGetValue(ScriptConstants.AzureFunctionsWebHookContextKey, out webHookContext))
                    {
                        // For WebHooks we want to use the WebHook library conversion methods
                        // Stuff the resolved data into the request context so the HttpTrigger binding
                        // can access it
                        var webHookData = GetWebHookData(triggerParameter.Type, webHookContext);
                        request.Properties.Add(ScriptConstants.AzureFunctionsWebHookDataKey, webHookData);
                    }
                }

                // see if the function defines a parameter to receive the HttpRequestMessage and
                // if so, pass it along
                ParameterDescriptor requestParameter = function.Parameters.FirstOrDefault(p => p.Type == typeof(HttpRequestMessage));
                if (requestParameter != null)
                {
                    arguments.Add(requestParameter.Name, request);
                }
            }

            arguments.Add(triggerParameter.Name, request);

            return(arguments);
        }
Ejemplo n.º 2
0
        protected override void OnHostStarted()
        {
            base.OnHostStarted();

            // whenever the host is created (or recreated) we build a cache map of
            // all http function routes
            HttpFunctions = new Dictionary <string, FunctionDescriptor>();
            foreach (var function in Instance.Functions)
            {
                HttpTriggerBindingMetadata httpTriggerBinding = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.SingleOrDefault(p => p.Type == BindingType.HttpTrigger);
                if (httpTriggerBinding != null)
                {
                    string route = httpTriggerBinding.Route;
                    if (!string.IsNullOrEmpty(route))
                    {
                        route += "/";
                    }
                    route += function.Name;

                    HttpFunctions.Add(route.ToLowerInvariant(), function);
                }
            }

            // Purge any old Function secrets
            _secretManager.PurgeOldFiles(Instance.ScriptConfig.RootScriptPath);
        }
        public async Task <HttpResponseMessage> HandleRequestAsync(FunctionDescriptor function, HttpRequestMessage request, Func <HttpRequestMessage, Task <HttpResponseMessage> > invokeFunction)
        {
            // First check if there is a registered WebHook Receiver for this request, and if
            // so use it
            HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => string.Compare("HttpTrigger", p.Type, StringComparison.OrdinalIgnoreCase) == 0);
            string           webHookReceiver = httpFunctionMetadata.WebHookType;
            IWebHookReceiver receiver        = null;

            if (string.IsNullOrEmpty(webHookReceiver) || !_receiverLookup.TryGetValue(webHookReceiver, out receiver))
            {
                // The function is not correctly configured. Log an error and return 500
                string configurationError = string.Format(CultureInfo.InvariantCulture, "Invalid WebHook configuration. Unable to find a receiver for WebHook type '{0}'", webHookReceiver);
                function.Invoker.OnError(new FunctionInvocationException(configurationError));

                return(new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError));
            }

            // if the code value is specified via header rather than query string
            // promote it to the query string (that's what the WebHook library expects)
            ApplyHeaderValuesToQuery(request);

            HttpRequestContext context = new HttpRequestContext
            {
                Configuration = _httpConfiguration
            };

            request.SetConfiguration(_httpConfiguration);

            // add the anonymous handler function from above to the request properties
            // so our custom WebHookHandler can invoke it at the right time
            request.Properties.Add(AzureFunctionsCallbackKey, invokeFunction);

            // Request content can't be read multiple
            // times, so this forces it to buffer
            await request.Content.LoadIntoBufferAsync();

            string receiverId = function.Name.ToLowerInvariant();

            // Get an optional client ID. This information is passed as the receiver ID, allowing
            // the receiver config to map configuration based on the client ID (primarily used for secret resolution).
            string clientId = GetClientID(request);

            string webhookId = $"{receiverId},{clientId}";

            return(await receiver.ReceiveAsync(webhookId, context, request));
        }
Ejemplo n.º 4
0
        private async Task <HttpResponseMessage> ProcessRequestAsync(HttpRequestMessage request, FunctionDescriptor function, CancellationToken cancellationToken)
        {
            HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => string.Compare("HttpTrigger", p.Type, StringComparison.OrdinalIgnoreCase) == 0);
            bool isWebHook               = !string.IsNullOrEmpty(httpFunctionMetadata.WebHookType);
            var  authorizationLevel      = request.GetAuthorizationLevel();
            HttpResponseMessage response = null;

            if (isWebHook)
            {
                if (authorizationLevel == AuthorizationLevel.Admin)
                {
                    // Admin level requests bypass the WebHook auth pipeline
                    response = await _scriptHostManager.HandleRequestAsync(function, request, cancellationToken);
                }
                else
                {
                    // This is a WebHook request so define a delegate for the user function.
                    // The WebHook Receiver pipeline will first validate the request fully
                    // then invoke this callback.
                    Func <HttpRequestMessage, Task <HttpResponseMessage> > invokeFunction = async(req) =>
                    {
                        // Reset the content stream before passing the request down to the function
                        Stream stream = await req.Content.ReadAsStreamAsync();

                        stream.Seek(0, SeekOrigin.Begin);

                        return(await _scriptHostManager.HandleRequestAsync(function, req, cancellationToken));
                    };
                    response = await _webHookReceiverManager.HandleRequestAsync(function, request, invokeFunction);
                }
            }
            else
            {
                // Authorize
                if (authorizationLevel < httpFunctionMetadata.AuthLevel)
                {
                    return(new HttpResponseMessage(HttpStatusCode.Unauthorized));
                }

                // Not a WebHook request so dispatch directly
                response = await _scriptHostManager.HandleRequestAsync(function, request, cancellationToken);
            }

            return(response);
        }
        // A route is in conflict if the route matches any other existing
        // route and there is intersection in the http methods of the two functions
        internal static bool HttpRoutesConflict(HttpTriggerBindingMetadata functionMetadata, HttpTriggerBindingMetadata otherFunctionMetadata)
        {
            if (string.Compare(functionMetadata.Route.Trim('/'), otherFunctionMetadata.Route.Trim('/'), StringComparison.OrdinalIgnoreCase) != 0)
            {
                // routes differ, so no conflict
                return(false);
            }

            if (functionMetadata.Methods == null || functionMetadata.Methods.Count == 0 ||
                otherFunctionMetadata.Methods == null || otherFunctionMetadata.Methods.Count == 0)
            {
                // if either methods collection is null or empty that means
                // "all methods", which will intersect with any method collection
                return(true);
            }

            return(functionMetadata.Methods.Intersect(otherFunctionMetadata.Methods).Any());
        }
Ejemplo n.º 6
0
        public void GenerateHttpTriggerFunction()
        {
            HttpTriggerBindingMetadata trigger = new HttpTriggerBindingMetadata
            {
                Type = BindingType.HttpTrigger
            };
            MethodInfo method = GenerateMethod(trigger);

            VerifyCommonProperties(method);

            // verify trigger parameter
            ParameterInfo parameter = method.GetParameters()[0];

            Assert.Equal("req", parameter.Name);
            Assert.Equal(typeof(HttpRequestMessage), parameter.ParameterType);
            NoAutomaticTriggerAttribute attribute = method.GetCustomAttribute <NoAutomaticTriggerAttribute>();

            Assert.NotNull(attribute);
        }
Ejemplo n.º 7
0
        internal void InitializeHttpFunctions(Collection <FunctionDescriptor> functions)
        {
            HttpFunctions = new Dictionary <string, FunctionDescriptor>();
            foreach (var function in functions)
            {
                HttpTriggerBindingMetadata httpTriggerBinding = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.SingleOrDefault(p => string.Compare("HttpTrigger", p.Type, StringComparison.OrdinalIgnoreCase) == 0);
                if (httpTriggerBinding != null)
                {
                    string route = httpTriggerBinding.Route;
                    if (!string.IsNullOrEmpty(route))
                    {
                        route += "/";
                    }
                    route += function.Name;

                    HttpFunctions.Add(route.ToLowerInvariant(), function);
                }
            }
        }
Ejemplo n.º 8
0
        private static async Task <Dictionary <string, object> > GetFunctionArgumentsAsync(FunctionDescriptor function, HttpRequestMessage request)
        {
            ParameterDescriptor         triggerParameter = function.Parameters.First(p => p.IsTrigger);
            Dictionary <string, object> arguments        = new Dictionary <string, object>();
            object triggerArgument = null;

            if (triggerParameter.Type == typeof(HttpRequestMessage))
            {
                triggerArgument = request;
            }
            else
            {
                // We'll replace the trigger argument but still want to flow the request
                // so add it to the arguments, as a system argument
                arguments.Add(ScriptConstants.DefaultSystemTriggerParameterName, request);

                HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => p.Type == BindingType.HttpTrigger);
                if (!string.IsNullOrEmpty(httpFunctionMetadata.WebHookType))
                {
                    WebHookHandlerContext webHookContext;
                    if (request.Properties.TryGetValue(ScriptConstants.AzureFunctionsWebHookContextKey, out webHookContext))
                    {
                        triggerArgument = GetWebHookData(triggerParameter.Type, webHookContext);
                    }
                }

                if (triggerArgument == null)
                {
                    triggerArgument = await request.Content.ReadAsAsync(triggerParameter.Type);
                }
            }

            arguments.Add(triggerParameter.Name, triggerArgument);

            return(arguments);
        }
        public async Task <HttpResponseMessage> HandleRequestAsync(FunctionDescriptor function, HttpRequestMessage request, Func <HttpRequestMessage, Task <HttpResponseMessage> > invokeFunction)
        {
            // First check if there is a registered WebHook Receiver for this request, and if
            // so use it
            HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => p.Type == BindingType.HttpTrigger);
            string           webHookReceiver = httpFunctionMetadata.WebHookType;
            IWebHookReceiver receiver        = null;

            if (string.IsNullOrEmpty(webHookReceiver) || !_receiverLookup.TryGetValue(webHookReceiver, out receiver))
            {
                // The function is not correctly configured. Log an error and return 500
                string configurationError = string.Format(CultureInfo.InvariantCulture, "Invalid WebHook configuration. Unable to find a receiver for WebHook type '{0}'", webHookReceiver);
                function.Invoker.OnError(new FunctionInvocationException(configurationError));

                return(new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError));
            }

            HttpRequestContext context = new HttpRequestContext
            {
                Configuration = _httpConfiguration
            };

            request.SetConfiguration(_httpConfiguration);

            // add the anonymous handler function from above to the request properties
            // so our custom WebHookHandler can invoke it at the right time
            request.Properties.Add(AzureFunctionsCallbackKey, invokeFunction);

            // Request content can't be read multiple
            // times, so this forces it to buffer
            await request.Content.LoadIntoBufferAsync();

            string receiverId = function.Name.ToLowerInvariant();

            return(await receiver.ReceiveAsync(receiverId, context, request));
        }
Ejemplo n.º 10
0
        public void HttpRoutesConflict_ReturnsExpectedResult()
        {
            var first = new HttpTriggerBindingMetadata
            {
                Route = "foo/bar/baz"
            };
            var second = new HttpTriggerBindingMetadata
            {
                Route = "foo/bar"
            };

            Assert.False(ScriptHost.HttpRoutesConflict(first, second));
            Assert.False(ScriptHost.HttpRoutesConflict(second, first));

            first = new HttpTriggerBindingMetadata
            {
                Route = "foo/bar/baz"
            };
            second = new HttpTriggerBindingMetadata
            {
                Route = "foo/bar/baz"
            };
            Assert.True(ScriptHost.HttpRoutesConflict(first, second));
            Assert.True(ScriptHost.HttpRoutesConflict(second, first));

            // no conflict since methods do not intersect
            first = new HttpTriggerBindingMetadata
            {
                Methods = new Collection <HttpMethod>()
                {
                    HttpMethod.Get, HttpMethod.Head
                },
                Route = "foo/bar/baz"
            };
            second = new HttpTriggerBindingMetadata
            {
                Methods = new Collection <HttpMethod>()
                {
                    HttpMethod.Post, HttpMethod.Put
                },
                Route = "foo/bar/baz"
            };
            Assert.False(ScriptHost.HttpRoutesConflict(first, second));
            Assert.False(ScriptHost.HttpRoutesConflict(second, first));

            first = new HttpTriggerBindingMetadata
            {
                Methods = new Collection <HttpMethod>()
                {
                    HttpMethod.Get, HttpMethod.Head
                },
                Route = "foo/bar/baz"
            };
            second = new HttpTriggerBindingMetadata
            {
                Route = "foo/bar/baz"
            };
            Assert.True(ScriptHost.HttpRoutesConflict(first, second));
            Assert.True(ScriptHost.HttpRoutesConflict(second, first));

            first = new HttpTriggerBindingMetadata
            {
                Methods = new Collection <HttpMethod>()
                {
                    HttpMethod.Get, HttpMethod.Head, HttpMethod.Put, HttpMethod.Post
                },
                Route = "foo/bar/baz"
            };
            second = new HttpTriggerBindingMetadata
            {
                Methods = new Collection <HttpMethod>()
                {
                    HttpMethod.Put
                },
                Route = "foo/bar/baz"
            };
            Assert.True(ScriptHost.HttpRoutesConflict(first, second));
            Assert.True(ScriptHost.HttpRoutesConflict(second, first));
        }
        public override async Task <HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
        {
            HttpRequestMessage request = controllerContext.Request;

            // First see if the request maps to an HTTP function
            FunctionDescriptor function = _scriptHostManager.GetHttpFunctionOrNull(request.RequestUri);

            if (function == null)
            {
                return(new HttpResponseMessage(HttpStatusCode.NotFound));
            }

            // Determine the authorization level of the request
            SecretManager      secretManager      = controllerContext.Configuration.DependencyResolver.GetService <SecretManager>();
            AuthorizationLevel authorizationLevel = AuthorizationLevelAttribute.GetAuthorizationLevel(request, secretManager, functionName: function.Name);

            if (function.Metadata.IsExcluded ||
                (function.Metadata.IsDisabled && authorizationLevel != AuthorizationLevel.Admin))
            {
                // disabled functions are not publically addressable w/o Admin level auth,
                // and excluded functions are also ignored here (though the check above will
                // already exclude them)
                return(new HttpResponseMessage(HttpStatusCode.NotFound));
            }

            // Dispatch the request
            HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => string.Compare("HttpTrigger", p.Type, StringComparison.OrdinalIgnoreCase) == 0);
            bool isWebHook = !string.IsNullOrEmpty(httpFunctionMetadata.WebHookType);
            HttpResponseMessage response = null;

            if (isWebHook)
            {
                if (authorizationLevel == AuthorizationLevel.Admin)
                {
                    // Admin level requests bypass the WebHook auth pipeline
                    response = await _scriptHostManager.HandleRequestAsync(function, request, cancellationToken);
                }
                else
                {
                    // This is a WebHook request so define a delegate for the user function.
                    // The WebHook Receiver pipeline will first validate the request fully
                    // then invoke this callback.
                    Func <HttpRequestMessage, Task <HttpResponseMessage> > invokeFunction = async(req) =>
                    {
                        // Reset the content stream before passing the request down to the function
                        Stream stream = await req.Content.ReadAsStreamAsync();

                        stream.Seek(0, SeekOrigin.Begin);

                        return(await _scriptHostManager.HandleRequestAsync(function, req, cancellationToken));
                    };
                    response = await _webHookReceiverManager.HandleRequestAsync(function, request, invokeFunction);
                }
            }
            else
            {
                // Authorize
                if (authorizationLevel < httpFunctionMetadata.AuthLevel)
                {
                    return(new HttpResponseMessage(HttpStatusCode.Unauthorized));
                }

                // Validate the HttpMethod
                // Note that for WebHook requests, WebHook receiver does its own validation
                if (httpFunctionMetadata.Methods != null && !httpFunctionMetadata.Methods.Contains(request.Method))
                {
                    return(new HttpResponseMessage(HttpStatusCode.MethodNotAllowed));
                }

                // Not a WebHook request so dispatch directly
                response = await _scriptHostManager.HandleRequestAsync(function, request, cancellationToken);
            }

            return(response);
        }