public Task <ITriggerBinding> TryCreateAsync(TriggerBindingProviderContext context) { if (context == null) { throw new ArgumentNullException("context"); } ParameterInfo parameter = context.Parameter; HttpTriggerAttribute attribute = parameter.GetCustomAttribute <HttpTriggerAttribute>(inherit: false); if (attribute == null) { return(Task.FromResult <ITriggerBinding>(null)); } // Can bind to user types, HttpRequestMessage, object (for dynamic binding support) and all the Read // Types supported by StreamValueBinder IEnumerable <Type> supportedTypes = StreamValueBinder.GetSupportedTypes(FileAccess.Read) .Union(new Type[] { typeof(HttpRequest), typeof(object), typeof(HttpRequestMessage) }); bool isSupportedTypeBinding = ValueBinder.MatchParameterType(parameter, supportedTypes); bool isUserTypeBinding = !isSupportedTypeBinding && IsValidUserType(parameter.ParameterType); if (!isSupportedTypeBinding && !isUserTypeBinding) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Can't bind HttpTriggerAttribute to type '{0}'.", parameter.ParameterType)); } return(Task.FromResult <ITriggerBinding>(new HttpTriggerBinding(attribute, context.Parameter, isUserTypeBinding, _responseHook))); }
public void ValidateFunction_ThrowsOnDuplicateName() { var httpFunctions = new Dictionary <string, HttpTriggerAttribute>(); var name = "test"; // first add an http function var metadata = new FunctionMetadata(); var function = new Mock <FunctionDescriptor>(MockBehavior.Strict, name, null, metadata, null, null, null, null); var attribute = new HttpTriggerAttribute(AuthorizationLevel.Function, "get"); function.Setup(p => p.GetTriggerAttributeOrNull <HttpTriggerAttribute>()).Returns(() => attribute); ScriptHost.ValidateFunction(function.Object, httpFunctions); // add a proxy with same name metadata = new FunctionMetadata() { IsProxy = true }; function = new Mock <FunctionDescriptor>(MockBehavior.Strict, name, null, metadata, null, null, null, null); attribute = new HttpTriggerAttribute(AuthorizationLevel.Function, "get") { Route = "proxyRoute" }; function.Setup(p => p.GetTriggerAttributeOrNull <HttpTriggerAttribute>()).Returns(() => attribute); var ex = Assert.Throws <InvalidOperationException>(() => { ScriptHost.ValidateFunction(function.Object, httpFunctions); }); Assert.Equal(string.Format($"The function or proxy name '{name}' must be unique within the function app.", name), ex.Message); }
public void Constructor_AuthLevelOnly_ReturnsExpectedResult() { var attrib = new HttpTriggerAttribute(AuthorizationLevel.Admin); Assert.Equal(AuthorizationLevel.Admin, attrib.AuthLevel); Assert.Null(attrib.Methods); }
public Task <ITriggerBinding> TryCreateAsync(TriggerBindingProviderContext context) { if (context == null) { throw new ArgumentNullException("context"); } ParameterInfo parameter = context.Parameter; HttpTriggerAttribute attribute = parameter.GetCustomAttribute <HttpTriggerAttribute>(inherit: false); if (attribute == null) { return(Task.FromResult <ITriggerBinding>(null)); } bool isSupportedTypeBinding = ValueBinder.MatchParameterType(parameter, _supportedTypes); bool isUserTypeBinding = !isSupportedTypeBinding && IsValidUserType(parameter.ParameterType); if (!isSupportedTypeBinding && !isUserTypeBinding) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Can't bind HttpTriggerAttribute to type '{0}'.", parameter.ParameterType)); } return(Task.FromResult <ITriggerBinding>(new HttpTriggerBinding(attribute, context.Parameter, isUserTypeBinding, _responseHook))); }
public void HttpRoutesConflict_ReturnsExpectedResult() { var first = new HttpTriggerAttribute { Route = "foo/bar/baz" }; var second = new HttpTriggerAttribute { Route = "foo/bar" }; Assert.False(ScriptHost.HttpRoutesConflict(first, second)); Assert.False(ScriptHost.HttpRoutesConflict(second, first)); first = new HttpTriggerAttribute { Route = "foo/bar/baz" }; second = new HttpTriggerAttribute { 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 HttpTriggerAttribute(AuthorizationLevel.Function, "get", "head") { Route = "foo/bar/baz" }; second = new HttpTriggerAttribute(AuthorizationLevel.Function, "post", "put") { Route = "foo/bar/baz" }; Assert.False(ScriptHost.HttpRoutesConflict(first, second)); Assert.False(ScriptHost.HttpRoutesConflict(second, first)); first = new HttpTriggerAttribute(AuthorizationLevel.Function, "get", "head") { Route = "foo/bar/baz" }; second = new HttpTriggerAttribute { Route = "foo/bar/baz" }; Assert.True(ScriptHost.HttpRoutesConflict(first, second)); Assert.True(ScriptHost.HttpRoutesConflict(second, first)); first = new HttpTriggerAttribute(AuthorizationLevel.Function, "get", "head", "put", "post") { Route = "foo/bar/baz" }; second = new HttpTriggerAttribute(AuthorizationLevel.Function, "put") { Route = "foo/bar/baz" }; Assert.True(ScriptHost.HttpRoutesConflict(first, second)); Assert.True(ScriptHost.HttpRoutesConflict(second, first)); }
/// <inheritdoc /> public OperationType GetHttpVerb(HttpTriggerAttribute trigger) { var verb = Enum.TryParse <OperationType>(trigger.Methods.First(), true, out OperationType ot) ? ot : throw new InvalidOperationException(); return(verb); }
public static string GetRouteOrDefault(this HttpTriggerAttribute trigger, string defaultRoute = "/") { if (string.IsNullOrWhiteSpace(defaultRoute)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(defaultRoute)); } return(trigger.Route?.AsPath() ?? defaultRoute.AsPath()); }
protected static void MakeParameterString(HttpTriggerAttribute trigger, List <OpenApiParameter> parameters, HttpMethod httpMethod, string route, ParameterLocation location, string name) { FixParameter(trigger, parameters, httpMethod, route, location, name, (parameter) => { parameter.Schema = new OpenApiSchema { Type = "string", Format = "string" }; }); }
public void Constructor_MethodsOnly_ReturnsExpectedResult() { var attrib = new HttpTriggerAttribute("GET", "POST"); Assert.Equal(AuthorizationLevel.Function, attrib.AuthLevel); Assert.Equal(2, attrib.Methods.Length); Assert.Equal("GET", attrib.Methods[0]); Assert.Equal("POST", attrib.Methods[1]); }
//https://github.com/Azure/azure-functions-vs-build-sdk/issues/16 public void HttpTriggerShouldHaveStringForAuthLevelEnum() { var attribute = new HttpTriggerAttribute(AuthorizationLevel.Function); var jObject = attribute.ToJObject(); jObject.Should().HaveElement("authLevel"); jObject["authLevel"].Should().Be("function"); }
public void WithRouteSpecified_ReturnsRouteAsPath() { var trigger = new HttpTriggerAttribute("get") { Route = "/mypath" }; Assert.Equal("/mypath", trigger.GetRouteOrDefault()); }
protected static void FixParameter(HttpTriggerAttribute trigger, List <OpenApiParameter> parameters, HttpMethod httpMethod, string route, ParameterLocation location, string name, Action <OpenApiParameter> fix) { if (trigger.Route != route || !trigger.Methods.Contains(httpMethod.ToString())) { return; } var parameter = parameters.First(p => p.In == location && p.Name == name); fix(parameter); }
public void HttpTriggerAttributeWithWebHookTypeShouldntHaveAnAuthLevel() { var attribute = new HttpTriggerAttribute() { WebHookType = "something" }; var jObject = attribute.ToJObject(); jObject["authLevel"].Should().BeNull(); }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FunctionAuthorizationRequirement requirement, FunctionDescriptor resource) { HttpTriggerAttribute httpTrigger = resource.GetTriggerAttributeOrNull <HttpTriggerAttribute>(); if (httpTrigger != null && PrincipalHasAuthLevelClaim(context.User, httpTrigger.AuthLevel)) { context.Succeed(requirement); } return(Task.CompletedTask); }
/// <inheritdoc /> public List <OpenApiParameter> GetOpenApiParameters(MethodInfo element, HttpTriggerAttribute trigger) { var parameters = element.GetCustomAttributes <OpenApiParameterAttribute>(inherit: false) .Select(p => p.ToOpenApiParameter()) .ToList(); if (trigger.AuthLevel != AuthorizationLevel.Anonymous) { parameters.AddOpenApiParameter <string>("code", @in: ParameterLocation.Query, required: false); } return(parameters); }
/// <inheritdoc /> public List <OpenApiParameter> GetOpenApiParameters(MethodInfo element, HttpTriggerAttribute trigger, NamingStrategy namingStrategy = null) { var parameters = element.GetCustomAttributes <OpenApiParameterAttribute>(inherit: false) .Select(p => p.ToOpenApiParameter(namingStrategy)) .ToList(); // This needs to be provided separately. if (trigger.AuthLevel != AuthorizationLevel.Anonymous) { parameters.AddOpenApiParameter <string>("code", @in: ParameterLocation.Query, required: false); } return(parameters); }
AuthorizationLevel GetAuthorizationLevel(OperationFilterContext Context) { foreach (ParameterInfo Parameter in Context.MethodInfo.GetParameters()) { HttpTriggerAttribute Attribute = Parameter.GetCustomAttribute <HttpTriggerAttribute>(); if (Attribute != null) { return(Attribute.AuthLevel); } } throw new Exception(); }
/// <summary> /// Gets the static strongly typed binding data contract /// </summary> internal Dictionary <string, Type> GetBindingDataContract(HttpTriggerAttribute attribute, ParameterInfo parameter) { // add contract members for POCO binding properties if binding to a POCO var aggregateDataContract = new Dictionary <string, Type>(StringComparer.OrdinalIgnoreCase); if (_isUserTypeBinding && _bindingDataProvider?.Contract != null) { aggregateDataContract.AddRange(_bindingDataProvider.Contract); } // Mark that HttpTrigger accepts a return value. aggregateDataContract["$return"] = typeof(object).MakeByRefType(); // add contract members for any route parameters if (!string.IsNullOrEmpty(attribute.Route)) { var routeParameters = TemplateParser.Parse(attribute.Route).Parameters; var parameters = ((MethodInfo)parameter.Member).GetParameters().ToDictionary(p => p.Name, p => p.ParameterType, StringComparer.OrdinalIgnoreCase); foreach (TemplatePart routeParameter in routeParameters) { // don't override if the contract already includes a name if (!aggregateDataContract.ContainsKey(routeParameter.Name)) { // if there is a method parameter mapped to this parameter // derive the Type from that Type type; if (!parameters.TryGetValue(routeParameter.Name, out type)) { type = typeof(string); } aggregateDataContract[routeParameter.Name] = type; } } } // add headers collection to the contract if (!aggregateDataContract.ContainsKey(HttpHeadersKey)) { aggregateDataContract.Add(HttpHeadersKey, typeof(IDictionary <string, string>)); } // add query parameter collection to binding contract if (!aggregateDataContract.ContainsKey(HttpQueryKey)) { aggregateDataContract.Add(HttpQueryKey, typeof(IDictionary <string, string>)); } return(aggregateDataContract); }
protected static void FixEnumParameter <TEnum>(HttpTriggerAttribute trigger, List <OpenApiParameter> parameters, HttpMethod httpMethod, string route, ParameterLocation location, string name) where TEnum : struct, IConvertible { if (!typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); } FixParameter(trigger, parameters, httpMethod, route, location, name, (parameter) => { parameter.Schema = new OpenApiSchema { Enum = GetEnumValues <TEnum>(), Type = "string", Format = "string" }; }); }
private HttpTriggerAttribute FindHttpTriggerAttribute(MethodInfo methodInfo) { HttpTriggerAttribute triggerAttribute = null; foreach (var parameter in methodInfo.GetParameters()) { triggerAttribute = parameter.GetCustomAttributes(typeof(HttpTriggerAttribute), false) .FirstOrDefault() as HttpTriggerAttribute; if (triggerAttribute != null) { break; } } return(triggerAttribute); }
/// <inheritdoc /> public List <OpenApiParameter> GetOpenApiParameters(MethodInfo element, HttpTriggerAttribute trigger, NamingStrategy namingStrategy, VisitorCollection collection) { var parameters = element.GetCustomAttributes <OpenApiParameterAttribute>(inherit: false) .Where(p => p.Deprecated == false) .Select(p => p.ToOpenApiParameter(namingStrategy, collection)) .ToList(); // // TODO: Should this be forcibly provided? // // This needs to be provided separately. // if (trigger.AuthLevel != AuthorizationLevel.Anonymous) // { // parameters.AddOpenApiParameter<string>("code", @in: ParameterLocation.Query, required: false); // } return(parameters); }
internal static void ValidateHttpFunction(string functionName, HttpTriggerAttribute httpTrigger, bool isProxy = false) { if (string.IsNullOrWhiteSpace(httpTrigger.Route) && !isProxy) { // if no explicit route is provided, default to the function name httpTrigger.Route = functionName; } // disallow custom routes in our own reserved route space string httpRoute = httpTrigger.Route.Trim('/').ToLowerInvariant(); if (httpRoute.StartsWith("admin")) { throw new InvalidOperationException("The specified route conflicts with one or more built in routes."); } }
public HttpTriggerBinding(HttpTriggerAttribute attribute, ParameterInfo parameter, bool isUserTypeBinding, Action <HttpRequest, object> responseHook = null) { _responseHook = responseHook; _parameter = parameter; _isUserTypeBinding = isUserTypeBinding; if (_isUserTypeBinding) { // Create the BindingDataProvider from the user Type. The BindingDataProvider // is used to define the binding parameters that the binding exposes to other // bindings (i.e. the properties of the POCO can be bound to by other bindings). // It is also used to extract the binding data from an instance of the Type. _bindingDataProvider = BindingDataProvider.FromType(parameter.ParameterType); } _bindingDataContract = GetBindingDataContract(attribute, parameter); }
private bool TryGetHttpTrigger(MethodInfo methodInfo, out HttpTriggerAttribute triggerAttribute) { triggerAttribute = null; var ignore = methodInfo.GetCustomAttributes().Any(x => x is SwaggerIgnoreAttribute); if (ignore) { return(false); } triggerAttribute = FindHttpTriggerAttribute(methodInfo); if (triggerAttribute == null) { return(false); } return(true); }
// 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(HttpTriggerAttribute httpTrigger, HttpTriggerAttribute otherHttpTrigger) { if (string.Compare(httpTrigger.Route.Trim('/'), otherHttpTrigger.Route.Trim('/'), StringComparison.OrdinalIgnoreCase) != 0) { // routes differ, so no conflict return(false); } if (httpTrigger.Methods == null || httpTrigger.Methods.Length == 0 || otherHttpTrigger.Methods == null || otherHttpTrigger.Methods.Length == 0) { // if either methods collection is null or empty that means // "all methods", which will intersect with any method collection return(true); } return(httpTrigger.Methods.Intersect(otherHttpTrigger.Methods).Any()); }
private static dynamic GeneratePaths(Assembly assembly, dynamic doc) { dynamic paths = new ExpandoObject(); var methods = assembly.GetTypes() .SelectMany(t => t.GetMethods()) .Where(m => m.GetCustomAttributes(typeof(FunctionNameAttribute), false).Length > 0) .ToArray(); foreach (MethodInfo methodInfo in methods) { string route = "/api/"; var functionAttr = (FunctionNameAttribute)methodInfo.GetCustomAttributes(typeof(FunctionNameAttribute), false) .Single(); if (functionAttr.Name == SwaggerFunctionName) { continue; } HttpTriggerAttribute triggerAttribute = null; foreach (ParameterInfo parameter in methodInfo.GetParameters()) { triggerAttribute = parameter.GetCustomAttributes(typeof(HttpTriggerAttribute), false) .FirstOrDefault() as HttpTriggerAttribute; if (triggerAttribute != null) { break; } } if (triggerAttribute == null) { continue; // Trigger attribute is required in an Azure function } if (!string.IsNullOrWhiteSpace(triggerAttribute.Route)) { route += triggerAttribute.Route; } else { route += functionAttr.Name; } dynamic path = new ExpandoObject(); var verbs = triggerAttribute.Methods ?? new[] { "get", "post", "delete", "head", "patch", "put", "options" }; foreach (string verb in verbs) { dynamic operation = new ExpandoObject(); operation.operationId = ToTitleCase(functionAttr.Name) + ToTitleCase(verb); operation.produces = new[] { "application/json" }; operation.consumes = new[] { "application/json" }; operation.parameters = GenerateFunctionParametersSignature(methodInfo, route, doc); // Summary is title operation.summary = GetFunctionName(methodInfo, functionAttr.Name); // Verbose description operation.description = GetFunctionDescription(methodInfo, functionAttr.Name); operation.responses = GenerateResponseParameterSignature(methodInfo, doc); dynamic keyQuery = new ExpandoObject(); keyQuery.apikeyQuery = new string[0]; operation.security = new ExpandoObject[] { keyQuery }; // Microsoft Flow import doesn't like two apiKey options, so we leave one out. //dynamic apikeyHeader = new ExpandoObject(); //apikeyHeader.apikeyHeader = new string[0]; //operation.security = new ExpandoObject[] { keyQuery, apikeyHeader }; AddToExpando(path, verb, operation); } AddToExpando(paths, route, path); } return(paths); }
private static IList <string> GetMethods(HttpTriggerAttribute httpTriggerAttribute) { return((httpTriggerAttribute.Methods ?? _defaultMethods) .Select(m => m.Trim().ToUpperInvariant()) .ToList()); }
/// <inheritdoc /> public string GetHttpEndpoint(FunctionNameAttribute function, HttpTriggerAttribute trigger) { var endpoint = $"/{(string.IsNullOrWhiteSpace(trigger.Route) ? function.Name : this.FilterRoute(trigger.Route)).Trim('/')}"; return(endpoint); }
private dynamic GeneratePaths(Assembly assembly, dynamic doc, string apiTitle, string apiDefinitionName, string pathPrefix) { dynamic paths = new ExpandoObject(); var methods = assembly.GetTypes() .SelectMany(t => t.GetMethods()) .Where(m => m.GetCustomAttributes(typeof(FunctionNameAttribute), false).Length > 0).ToArray(); foreach (MethodInfo methodInfo in methods) { //hide any disabled methods if (methodInfo.GetCustomAttributes(typeof(DisableAttribute), true).Any() || methodInfo.GetCustomAttributes(typeof(SwaggerIgnoreAttribute), true).Any()) { continue; } var route = pathPrefix; var functionAttr = (FunctionNameAttribute)methodInfo.GetCustomAttributes(typeof(FunctionNameAttribute), false) .Single(); if (functionAttr.Name == apiDefinitionName) { continue; } HttpTriggerAttribute triggerAttribute = null; foreach (ParameterInfo parameter in methodInfo.GetParameters()) { triggerAttribute = parameter.GetCustomAttributes(typeof(HttpTriggerAttribute), false) .FirstOrDefault() as HttpTriggerAttribute; if (triggerAttribute != null) { break; } } if (triggerAttribute == null) { continue; // Trigger attribute is required in an Azure function } if (!string.IsNullOrWhiteSpace(triggerAttribute.Route)) { route += triggerAttribute.Route; } else { route += functionAttr.Name; } dynamic path = new ExpandoObject(); var verbs = triggerAttribute.Methods ?? new[] { "get", "post", "delete", "head", "patch", "put", "options" }; foreach (string verb in verbs) { dynamic operation = new ExpandoObject(); operation.operationId = ToTitleCase(functionAttr.Name); operation.produces = new[] { "application/json" }; operation.consumes = new[] { "application/json" }; operation.parameters = GenerateFunctionParametersSignature(methodInfo, route, doc); // Summary is title operation.summary = GetFunctionName(methodInfo, functionAttr.Name); // Verbose description operation.description = GetFunctionDescription(methodInfo, functionAttr.Name); operation.responses = GenerateResponseParameterSignature(methodInfo, doc); operation.tags = new[] { apiTitle }; dynamic keyQuery = new ExpandoObject(); keyQuery.apikeyQuery = new string[0]; operation.security = new ExpandoObject[] { keyQuery }; AddToExpando(path, verb, operation); } AddToExpando(paths, route, path); } return(paths); }
public void ValidateFunction_ValidatesHttpRoutes() { var httpFunctions = new Dictionary <string, HttpTriggerAttribute>(); // first add an http function var metadata = new FunctionMetadata(); var function = new Mock <FunctionDescriptor>(MockBehavior.Strict, "test", null, metadata, null, null, null, null); var attribute = new HttpTriggerAttribute(AuthorizationLevel.Function, "get") { Route = "products/{category}/{id?}" }; function.Setup(p => p.GetTriggerAttributeOrNull <HttpTriggerAttribute>()).Returns(() => attribute); ScriptHost.ValidateFunction(function.Object, httpFunctions); Assert.Equal(1, httpFunctions.Count); Assert.True(httpFunctions.ContainsKey("test")); // add another for a completely different route function = new Mock <FunctionDescriptor>(MockBehavior.Strict, "test2", null, metadata, null, null, null, null); attribute = new HttpTriggerAttribute(AuthorizationLevel.Function, "get") { Route = "/foo/bar/baz/" }; function.Setup(p => p.GetTriggerAttributeOrNull <HttpTriggerAttribute>()).Returns(() => attribute); ScriptHost.ValidateFunction(function.Object, httpFunctions); Assert.Equal(2, httpFunctions.Count); Assert.True(httpFunctions.ContainsKey("test2")); // add another that varies from another only by http methods function = new Mock <FunctionDescriptor>(MockBehavior.Strict, "test3", null, metadata, null, null, null, null); attribute = new HttpTriggerAttribute(AuthorizationLevel.Function, "put", "post") { Route = "/foo/bar/baz/" }; function.Setup(p => p.GetTriggerAttributeOrNull <HttpTriggerAttribute>()).Returns(() => attribute); ScriptHost.ValidateFunction(function.Object, httpFunctions); Assert.Equal(3, httpFunctions.Count); Assert.True(httpFunctions.ContainsKey("test3")); // now try to add a function for the same route // where the http methods overlap function = new Mock <FunctionDescriptor>(MockBehavior.Strict, "test4", null, metadata, null, null, null, null); attribute = new HttpTriggerAttribute { Route = "/foo/bar/baz/" }; function.Setup(p => p.GetTriggerAttributeOrNull <HttpTriggerAttribute>()).Returns(() => attribute); var ex = Assert.Throws <InvalidOperationException>(() => { ScriptHost.ValidateFunction(function.Object, httpFunctions); }); Assert.Equal("The route specified conflicts with the route defined by function 'test2'.", ex.Message); Assert.Equal(3, httpFunctions.Count); // try to add a route under reserved admin route function = new Mock <FunctionDescriptor>(MockBehavior.Strict, "test5", null, metadata, null, null, null, null); attribute = new HttpTriggerAttribute { Route = "admin/foo/bar" }; function.Setup(p => p.GetTriggerAttributeOrNull <HttpTriggerAttribute>()).Returns(() => attribute); ex = Assert.Throws <InvalidOperationException>(() => { ScriptHost.ValidateFunction(function.Object, httpFunctions); }); Assert.Equal("The specified route conflicts with one or more built in routes.", ex.Message); // verify that empty route is defaulted to function name function = new Mock <FunctionDescriptor>(MockBehavior.Strict, "test6", null, metadata, null, null, null, null); attribute = new HttpTriggerAttribute(); function.Setup(p => p.GetTriggerAttributeOrNull <HttpTriggerAttribute>()).Returns(() => attribute); ScriptHost.ValidateFunction(function.Object, httpFunctions); Assert.Equal(4, httpFunctions.Count); Assert.True(httpFunctions.ContainsKey("test6")); Assert.Equal("test6", attribute.Route); }