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