diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Configurations/OpenApiHttpTriggerAuthorization.cs b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Configurations/OpenApiHttpTriggerAuthorization.cs new file mode 100644 index 00000000..17f5f00a --- /dev/null +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Configurations/OpenApiHttpTriggerAuthorization.cs @@ -0,0 +1,58 @@ +// using System.Globalization; +// using System.Linq; +// using System.Net; +// using System.Threading.Tasks; + +// using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +// using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; +// using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; + +// namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.Configurations +// { +// public class OpenApiHttpTriggerAuthorization : DefaultOpenApiHttpTriggerAuthorization +// { +// public override async Task AuthorizeAsync(IHttpRequestDataObject req) +// { +// var result = default(OpenApiAuthorizationResult); +// var authtoken = (string) req.Headers["Authorization"]; +// if (authtoken.IsNullOrWhiteSpace()) +// { +// result = new OpenApiAuthorizationResult() +// { +// StatusCode = HttpStatusCode.Unauthorized, +// ContentType = "text/plain", +// Payload = "Unauthorized", +// }; + +// return await Task.FromResult(result).ConfigureAwait(false); +// } + +// if (authtoken.StartsWith("Bearer", ignoreCase: true, CultureInfo.InvariantCulture) == false) +// { +// result = new OpenApiAuthorizationResult() +// { +// StatusCode = HttpStatusCode.Unauthorized, +// ContentType = "text/plain", +// Payload = "Invalid auth format", +// }; + +// return await Task.FromResult(result).ConfigureAwait(false); +// } + +// var token = authtoken.Split(' ').Last(); +// if (token != "secret") +// { +// result = new OpenApiAuthorizationResult() +// { +// StatusCode = HttpStatusCode.Forbidden, +// ContentType = "text/plain", +// Payload = "Invalid auth token", +// }; + +// return await Task.FromResult(result).ConfigureAwait(false); +// } + +// return await Task.FromResult(result).ConfigureAwait(false); +// } +// } +// } diff --git a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Extensions/OpenApiHostBuilderExtensions.cs b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Extensions/OpenApiHostBuilderExtensions.cs index 0b91e5d6..39a100fa 100644 --- a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Extensions/OpenApiHostBuilderExtensions.cs +++ b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Extensions/OpenApiHostBuilderExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Functions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Extensions/OpenApiHttpRequestDataExtensions.cs b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Extensions/OpenApiHttpRequestDataExtensions.cs index 47a8a99f..91bea663 100644 --- a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Extensions/OpenApiHttpRequestDataExtensions.cs +++ b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Extensions/OpenApiHttpRequestDataExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Internal; @@ -15,10 +16,44 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Extensions public static class OpenApiHttpRequestDataExtensions { /// - /// Gets the instance from the . + /// Gets the instance from the . /// /// instance. - /// Returns instance. + /// Returns instance. + public static IHeaderDictionary Headers(this HttpRequestData req) + { + req.ThrowIfNullOrDefault(); + + var headers = req.Headers.ToDictionary(p => p.Key, p => new StringValues(p.Value.ToArray())); + if (headers.IsNullOrDefault() || headers.Any() == false) + { + headers = new Dictionary(); + } + + return new HeaderDictionary(headers); + } + + /// + /// Gets the object from the header of . + /// + /// instance. + /// Header key. + /// Returns object. + public static StringValues Header(this HttpRequestData req, string key) + { + req.ThrowIfNullOrDefault(); + + var headers = Headers(req); + var value = headers.ContainsKey(key) ? headers[key] : new StringValues(default(string)); + + return value; + } + + /// + /// Gets the instance from the . + /// + /// instance. + /// Returns instance. public static IQueryCollection Queries(this HttpRequestData req) { req.ThrowIfNullOrDefault(); @@ -33,7 +68,7 @@ public static IQueryCollection Queries(this HttpRequestData req) } /// - /// Gets the object from the . + /// Gets the object from the querystring of . /// /// instance. /// Querystring key. diff --git a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Functions/OpenApiTriggerFunction.cs b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Functions/OpenApiTriggerFunction.cs index 8109e5ed..9844a442 100644 --- a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Functions/OpenApiTriggerFunction.cs +++ b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Functions/OpenApiTriggerFunction.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; using Microsoft.Extensions.Logging; @@ -37,20 +38,34 @@ public async Task RenderSwaggerDocument(HttpRequestData req, s log.LogInformation($"swagger.{extension} was requested."); var fi = new FileInfo(ctx.FunctionDefinition.PathToAssembly); + var request = new HttpRequestObject(req); var result = default(string); var response = default(HttpResponseData); try { - result = await (await this._context.SetApplicationAssemblyAsync(fi.Directory.FullName, appendBin: false)) - .Document - .InitialiseDocument() - .AddMetadata(this._context.OpenApiConfigurationOptions.Info) - .AddServer(new HttpRequestObject(req), this._context.HttpSettings.RoutePrefix, this._context.OpenApiConfigurationOptions) - .AddNamingStrategy(this._context.NamingStrategy) - .AddVisitors(this._context.GetVisitorCollection()) - .Build(this._context.ApplicationAssembly, this._context.OpenApiConfigurationOptions.OpenApiVersion) - .RenderAsync(this._context.GetOpenApiSpecVersion(this._context.OpenApiConfigurationOptions.OpenApiVersion), this._context.GetOpenApiFormat(extension)) - .ConfigureAwait(false); + var auth = await this._context + .SetApplicationAssemblyAsync(fi.Directory.FullName, appendBin: false) + .AuthorizeAsync(request) + .ConfigureAwait(false); + if (!auth.IsNullOrDefault()) + { + response = req.CreateResponse(auth.StatusCode); + response.Headers.Add("Content-Type", auth.ContentType); + await response.WriteStringAsync(auth.Payload).ConfigureAwait(false); + + return response; + } + + result = await this._context + .Document + .InitialiseDocument() + .AddMetadata(this._context.OpenApiConfigurationOptions.Info) + .AddServer(request, this._context.HttpSettings.RoutePrefix, this._context.OpenApiConfigurationOptions) + .AddNamingStrategy(this._context.NamingStrategy) + .AddVisitors(this._context.GetVisitorCollection()) + .Build(this._context.ApplicationAssembly, this._context.OpenApiConfigurationOptions.OpenApiVersion) + .RenderAsync(this._context.GetOpenApiSpecVersion(this._context.OpenApiConfigurationOptions.OpenApiVersion), this._context.GetOpenApiFormat(extension)) + .ConfigureAwait(false); response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", this._context.GetOpenApiFormat(extension).GetContentType()); @@ -82,20 +97,34 @@ public async Task RenderOpenApiDocument(HttpRequestData req, s log.LogInformation($"{version}.{extension} was requested."); var fi = new FileInfo(ctx.FunctionDefinition.PathToAssembly); + var request = new HttpRequestObject(req); var result = default(string); var response = default(HttpResponseData); try { - result = await (await this._context.SetApplicationAssemblyAsync(fi.Directory.FullName, appendBin: false)) - .Document - .InitialiseDocument() - .AddMetadata(this._context.OpenApiConfigurationOptions.Info) - .AddServer(new HttpRequestObject(req), this._context.HttpSettings.RoutePrefix, this._context.OpenApiConfigurationOptions) - .AddNamingStrategy(this._context.NamingStrategy) - .AddVisitors(this._context.GetVisitorCollection()) - .Build(this._context.ApplicationAssembly, this._context.GetOpenApiVersionType(version)) - .RenderAsync(this._context.GetOpenApiSpecVersion(version), this._context.GetOpenApiFormat(extension)) - .ConfigureAwait(false); + var auth = await this._context + .SetApplicationAssemblyAsync(fi.Directory.FullName, appendBin: false) + .AuthorizeAsync(request) + .ConfigureAwait(false); + if (!auth.IsNullOrDefault()) + { + response = req.CreateResponse(auth.StatusCode); + response.Headers.Add("Content-Type", auth.ContentType); + await response.WriteStringAsync(auth.Payload).ConfigureAwait(false); + + return response; + } + + result = await this._context + .Document + .InitialiseDocument() + .AddMetadata(this._context.OpenApiConfigurationOptions.Info) + .AddServer(request, this._context.HttpSettings.RoutePrefix, this._context.OpenApiConfigurationOptions) + .AddNamingStrategy(this._context.NamingStrategy) + .AddVisitors(this._context.GetVisitorCollection()) + .Build(this._context.ApplicationAssembly, this._context.GetOpenApiVersionType(version)) + .RenderAsync(this._context.GetOpenApiSpecVersion(version), this._context.GetOpenApiFormat(extension)) + .ConfigureAwait(false); response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", this._context.GetOpenApiFormat(extension).GetContentType()); @@ -126,17 +155,31 @@ public async Task RenderSwaggerUI(HttpRequestData req, Functio log.LogInformation("SwaggerUI page was requested."); var fi = new FileInfo(ctx.FunctionDefinition.PathToAssembly); + var request = new HttpRequestObject(req); var result = default(string); var response = default(HttpResponseData); try { - result = await (await this._context.SetApplicationAssemblyAsync(fi.Directory.FullName, appendBin: false)) - .SwaggerUI - .AddMetadata(this._context.OpenApiConfigurationOptions.Info) - .AddServer(new HttpRequestObject(req), this._context.HttpSettings.RoutePrefix, this._context.OpenApiConfigurationOptions) - .BuildAsync(this._context.PackageAssembly, this._context.OpenApiCustomUIOptions) - .RenderAsync("swagger.json", this._context.GetDocumentAuthLevel(), this._context.GetSwaggerAuthKey()) - .ConfigureAwait(false); + var auth = await this._context + .SetApplicationAssemblyAsync(fi.Directory.FullName, appendBin: false) + .AuthorizeAsync(request) + .ConfigureAwait(false); + if (!auth.IsNullOrDefault()) + { + response = req.CreateResponse(auth.StatusCode); + response.Headers.Add("Content-Type", auth.ContentType); + await response.WriteStringAsync(auth.Payload).ConfigureAwait(false); + + return response; + } + + result = await this._context + .SwaggerUI + .AddMetadata(this._context.OpenApiConfigurationOptions.Info) + .AddServer(request, this._context.HttpSettings.RoutePrefix, this._context.OpenApiConfigurationOptions) + .BuildAsync(this._context.PackageAssembly, this._context.OpenApiCustomUIOptions) + .RenderAsync("swagger.json", this._context.GetDocumentAuthLevel(), this._context.GetSwaggerAuthKey()) + .ConfigureAwait(false); response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", ContentTypeHtml); @@ -167,16 +210,21 @@ public async Task RenderOAuth2Redirect(HttpRequestData req, Fu log.LogInformation("The oauth2-redirect.html page was requested."); var fi = new FileInfo(ctx.FunctionDefinition.PathToAssembly); + var request = new HttpRequestObject(req); var result = default(string); var response = default(HttpResponseData); try { - result = await (await this._context.SetApplicationAssemblyAsync(fi.Directory.FullName, appendBin: false)) - .SwaggerUI - .AddServer(new HttpRequestObject(req), this._context.HttpSettings.RoutePrefix, this._context.OpenApiConfigurationOptions) - .BuildOAuth2RedirectAsync(this._context.PackageAssembly) - .RenderOAuth2RedirectAsync("oauth2-redirect.html", this._context.GetDocumentAuthLevel(), this._context.GetSwaggerAuthKey()) - .ConfigureAwait(false); + await this._context + .SetApplicationAssemblyAsync(fi.Directory.FullName, appendBin: false) + .ConfigureAwait(false); + + result = await this._context + .SwaggerUI + .AddServer(request, this._context.HttpSettings.RoutePrefix, this._context.OpenApiConfigurationOptions) + .BuildOAuth2RedirectAsync(this._context.PackageAssembly) + .RenderOAuth2RedirectAsync("oauth2-redirect.html", this._context.GetDocumentAuthLevel(), this._context.GetSwaggerAuthKey()) + .ConfigureAwait(false); response = req.CreateResponse(HttpStatusCode.OK); response.Headers.Add("Content-Type", ContentTypeHtml); diff --git a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/HttpRequestObject.cs b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/HttpRequestObject.cs index 1a1b8f48..2909305f 100644 --- a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/HttpRequestObject.cs +++ b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/HttpRequestObject.cs @@ -27,6 +27,7 @@ public HttpRequestObject(HttpRequestData req) ? new HostString(req.Url.Authority) : new HostString(req.Url.Host, req.Url.Port); + this.Headers = req.Headers(); this.Query = req.Queries(); this.Body = req.Body; } @@ -37,6 +38,9 @@ public HttpRequestObject(HttpRequestData req) /// public virtual HostString Host { get; } + /// + public virtual IHeaderDictionary Headers { get; } + /// public virtual IQueryCollection Query { get;} diff --git a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs index f1b38fb9..52137cea 100644 --- a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs +++ b/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/OpenApiHttpTriggerContext.cs @@ -140,6 +140,24 @@ public virtual async Task SetApplicationAssemblyAsyn return this; } + /// + public virtual async Task AuthorizeAsync(IHttpRequestDataObject req) + { + var result = default(OpenApiAuthorizationResult); + var type = this.ApplicationAssembly + .GetLoadableTypes() + .SingleOrDefault(p => p.HasInterface()); + if (type.IsNullOrDefault()) + { + return result; + } + + var auth = Activator.CreateInstance(type) as IOpenApiHttpTriggerAuthorization; + result = await auth.AuthorizeAsync(req).ConfigureAwait(false); + + return result; + } + /// public virtual VisitorCollection GetVisitorCollection() { diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IHttpRequestDataObject.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IHttpRequestDataObject.cs index e1da789c..3484020e 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IHttpRequestDataObject.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IHttpRequestDataObject.cs @@ -19,10 +19,15 @@ public interface IHttpRequestDataObject /// HostString Host { get; } + /// + /// Gets the header collection. + /// + IHeaderDictionary Headers { get; } + /// /// Gets the query collection. /// - IQueryCollection Query { get;} + IQueryCollection Query { get; } /// /// Gets the request payload stream. diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IOpenApiHttpTriggerAuthorization.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IOpenApiHttpTriggerAuthorization.cs new file mode 100644 index 00000000..5336fd1f --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IOpenApiHttpTriggerAuthorization.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions +{ + /// + /// This provides interfaces to HTTP trigger authorisation for OpenAPI endpoints. + /// + public interface IOpenApiHttpTriggerAuthorization + { + /// + /// Authorizes the endpoint. + /// + /// instance. + /// Returns instance. + Task AuthorizeAsync(IHttpRequestDataObject req); + } +} diff --git a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/IOpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IOpenApiHttpTriggerContext.cs similarity index 90% rename from src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/IOpenApiHttpTriggerContext.cs rename to src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IOpenApiHttpTriggerContext.cs index c52ca7fd..c299a4bc 100644 --- a/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/IOpenApiHttpTriggerContext.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Abstractions/IOpenApiHttpTriggerContext.cs @@ -2,7 +2,6 @@ using System.Reflection; using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors; @@ -10,10 +9,10 @@ using Newtonsoft.Json.Serialization; -namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions { /// - /// This provides interfaces to . + /// This provides interfaces to OpenApiHttpTriggerContext /// public interface IOpenApiHttpTriggerContext { @@ -74,8 +73,16 @@ public interface IOpenApiHttpTriggerContext /// /// Function app directory. /// Value indicating whether to append the "bin" directory or not. + /// Returns the instance. Task SetApplicationAssemblyAsync(string functionAppDirectory, bool appendBin = true); + /// + /// Authorizes the endpoint. + /// + /// instance. + /// Returns instance. + Task AuthorizeAsync(IHttpRequestDataObject req); + /// /// Gets the instance. /// diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiHttpTriggerAuthorization.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiHttpTriggerAuthorization.cs new file mode 100644 index 00000000..b999a261 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiHttpTriggerAuthorization.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations +{ + /// + /// This represents the entity for the default authorisation options for the HTTP trigger endpoints used for Swagger UI and OpenAPI document. + /// + public class DefaultOpenApiHttpTriggerAuthorization : IOpenApiHttpTriggerAuthorization + { + /// + public virtual async Task AuthorizeAsync(IHttpRequestDataObject req) + { + var result = default(OpenApiAuthorizationResult); + + return await Task.FromResult(result).ConfigureAwait(false); + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/OpenApiAuthorizationResult.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/OpenApiAuthorizationResult.cs new file mode 100644 index 00000000..6658e64e --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/OpenApiAuthorizationResult.cs @@ -0,0 +1,25 @@ +using System.Net; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations +{ + /// + /// This represents the entity for the authorisation result. + /// + public class OpenApiAuthorizationResult + { + /// + /// Gets or sets the HTTP status code. + /// + public virtual HttpStatusCode StatusCode { get; set; } + + /// + /// Gets or sets the content type. + /// + public virtual string ContentType { get; set; } + + /// + /// Gets or sets the payload. + /// + public virtual string Payload { get; set; } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Extensions/OpenApiHttpTriggerContextExtensions.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Extensions/OpenApiHttpTriggerContextExtensions.cs new file mode 100644 index 00000000..ced66ea7 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Extensions/OpenApiHttpTriggerContextExtensions.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions +{ + /// + /// This represents the extension entity for . + /// + public static class OpenApiHttpTriggerContextExtensions + { + /// + /// Authorizes the endpoint. + /// + /// instance. + /// instance. + /// Returns instance. + public static async Task AuthorizeAsync(this Task context, IHttpRequestDataObject req) + { + req.ThrowIfNullOrDefault(); + + var instance = await context.ThrowIfNullOrDefault().ConfigureAwait(false); + + return await instance.AuthorizeAsync(req).ConfigureAwait(false); + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/HttpRequestObject.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/HttpRequestObject.cs index 97124a86..d82932b4 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/HttpRequestObject.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/HttpRequestObject.cs @@ -21,6 +21,7 @@ public HttpRequestObject(HttpRequest req) this.Scheme = req.Scheme; this.Host = req.Host; + this.Headers = req.Headers; this.Query = req.Query; this.Body = req.Body; } @@ -31,6 +32,9 @@ public HttpRequestObject(HttpRequest req) /// public virtual HostString Host { get; } + /// + public virtual IHeaderDictionary Headers { get; } + /// public virtual IQueryCollection Query { get;} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/IOpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/IOpenApiHttpTriggerContext.cs deleted file mode 100644 index e5601703..00000000 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/IOpenApiHttpTriggerContext.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Reflection; -using System.Threading.Tasks; - -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums; -using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Visitors; -using Microsoft.OpenApi; - -using Newtonsoft.Json.Serialization; - -namespace Microsoft.Azure.WebJobs.Extensions.OpenApi -{ - /// - /// This provides interfaces to . - /// - public interface IOpenApiHttpTriggerContext - { - /// - /// Gets the instance representing the Azure Functions app. - /// - Assembly ApplicationAssembly { get; } - - /// - /// Gets the instance representing the Azure Functions OpenAPI Extension package. - /// - Assembly PackageAssembly { get; } - - /// - /// Gets the instance. - /// - IOpenApiConfigurationOptions OpenApiConfigurationOptions { get; } - - /// - /// Gets the instance. - /// - IOpenApiCustomUIOptions OpenApiCustomUIOptions { get; } - - /// - /// Gets the instance. - /// - HttpSettings HttpSettings { get; } - - /// - /// Gets the instance. - /// - IDocument Document { get; } - - /// - /// Gets the instance. - /// - ISwaggerUI SwaggerUI { get; } - - /// - /// Gets the instance. - /// - NamingStrategy NamingStrategy { get; } - - /// - /// Gets the value indicating whether it's in the development environment or not. - /// - bool IsDevelopment { get; } - - /// - /// Gets the executing assembly. - /// - /// Returns the executing assembly. - [Obsolete("This method is obsolete.", error: true)] - Assembly GetExecutingAssembly(); - - /// - /// Sets the application assembly from the function app directory. - /// - /// Function app directory. - /// Value indicating whether to append the "bin" directory or not. - Task SetApplicationAssemblyAsync(string functionAppDirectory, bool appendBin = true); - - /// - /// Gets the instance. - /// - /// Returns the instance. - VisitorCollection GetVisitorCollection(); - - /// - /// Gets the value. - /// - /// OpenAPI spec version. It can be either v2 or v3. - /// Returns the value. - OpenApiVersionType GetOpenApiVersionType(string version = "v2"); - - /// - /// Gets the value. - /// - /// OpenAPI spec version. It can be either v2 or v3. - /// Returns the value. - OpenApiSpecVersion GetOpenApiSpecVersion(string version = "v2"); - - /// - /// Gets the value. - /// - /// value. - /// Returns the value. - OpenApiSpecVersion GetOpenApiSpecVersion(OpenApiVersionType version = OpenApiVersionType.V2); - - /// - /// Gets the value. - /// - /// OpenAPI document format. It can be either json or yaml. - /// Returns the value. - OpenApiFormat GetOpenApiFormat(string format = "json"); - - /// - /// Gets the value. - /// - /// value. - /// Returns the value. - OpenApiFormat GetOpenApiFormat(OpenApiFormatType format = OpenApiFormatType.Json); - - /// - /// Gets the auth level of the document rendering page endpoint. - /// - /// Environment variables key to look for. - /// Returns the auth level of the document rendering page endpoint. - OpenApiAuthLevelType GetDocumentAuthLevel(string key = "OpenApi__AuthLevel__Document"); - - /// - /// Gets the auth level of the UI rendering page endpoint. - /// - /// Environment variables key to look for. - /// Returns the auth level of the UI rendering page endpoint. - OpenApiAuthLevelType GetUIAuthLevel(string key = "OpenApi__AuthLevel__UI"); - - /// - /// Gets the API key for endpoints from environment variables. - /// - /// Environment variables key to look for. - /// Returns the API key for endpoints. - string GetSwaggerAuthKey(string key = "OpenApi__ApiKey"); - } -} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs index 937a3b9d..cd32c832 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiHttpTriggerContext.cs @@ -140,6 +140,24 @@ public virtual async Task SetApplicationAssemblyAsyn return this; } + /// + public virtual async Task AuthorizeAsync(IHttpRequestDataObject req) + { + var result = default(OpenApiAuthorizationResult); + var type = this.ApplicationAssembly + .GetLoadableTypes() + .SingleOrDefault(p => p.HasInterface()); + if (type.IsNullOrDefault()) + { + return result; + } + + var auth = Activator.CreateInstance(type) as IOpenApiHttpTriggerAuthorization; + result = await auth.AuthorizeAsync(req).ConfigureAwait(false); + + return result; + } + /// public virtual VisitorCollection GetVisitorCollection() { diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs index 5dd7a267..ed6261a3 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi/OpenApiTriggerFunctions.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; using Microsoft.Extensions.Logging; @@ -35,15 +36,30 @@ public static async Task RenderSwaggerDocument(HttpRequest req, s { log.LogInformation($"swagger.{extension} was requested."); + var request = new HttpRequestObject(req); var result = default(string); var content = default(ContentResult); try { - result = await (await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory)) - .Document + var auth = await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) + .AuthorizeAsync(request) + .ConfigureAwait(false); + if (!auth.IsNullOrDefault()) + { + content = new ContentResult() + { + Content = auth.Payload, + ContentType = auth.ContentType, + StatusCode = (int)auth.StatusCode, + }; + + return content; + } + + result = await context.Document .InitialiseDocument() .AddMetadata(context.OpenApiConfigurationOptions.Info) - .AddServer(new HttpRequestObject(req), context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) + .AddServer(request, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) .AddNamingStrategy(context.NamingStrategy) .AddVisitors(context.GetVisitorCollection()) .Build(context.ApplicationAssembly, context.OpenApiConfigurationOptions.OpenApiVersion) @@ -92,12 +108,27 @@ public static async Task RenderOpenApiDocument(HttpRequest req, s { log.LogInformation($"{version}.{extension} was requested."); + var request = new HttpRequestObject(req); var result = default(string); var content = default(ContentResult); try { - result = await (await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory)) - .Document + var auth = await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) + .AuthorizeAsync(request) + .ConfigureAwait(false); + if (!auth.IsNullOrDefault()) + { + content = new ContentResult() + { + Content = auth.Payload, + ContentType = auth.ContentType, + StatusCode = (int)auth.StatusCode, + }; + + return content; + } + + result = await context.Document .InitialiseDocument() .AddMetadata(context.OpenApiConfigurationOptions.Info) .AddServer(new HttpRequestObject(req), context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) @@ -147,12 +178,27 @@ public static async Task RenderSwaggerUI(HttpRequest req, Executi { log.LogInformation("SwaggerUI page was requested."); + var request = new HttpRequestObject(req); var result = default(string); var content = default(ContentResult); try { - result = await (await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory)) - .SwaggerUI + var auth = await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) + .AuthorizeAsync(request) + .ConfigureAwait(false); + if (!auth.IsNullOrDefault()) + { + content = new ContentResult() + { + Content = auth.Payload, + ContentType = auth.ContentType, + StatusCode = (int)auth.StatusCode, + }; + + return content; + } + + result = await context.SwaggerUI .AddMetadata(context.OpenApiConfigurationOptions.Info) .AddServer(new HttpRequestObject(req), context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) .BuildAsync(context.PackageAssembly, context.OpenApiCustomUIOptions) @@ -199,13 +245,16 @@ public static async Task RenderOAuth2Redirect(HttpRequest req, Ex { log.LogInformation("The oauth2-redirect.html page was requested."); + var request = new HttpRequestObject(req); var result = default(string); var content = default(ContentResult); try { - result = await (await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory)) - .SwaggerUI - .AddServer(new HttpRequestObject(req), context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) + await context.SetApplicationAssemblyAsync(ctx.FunctionAppDirectory) + .ConfigureAwait(false); + + result = await context.SwaggerUI + .AddServer(request, context.HttpSettings.RoutePrefix, context.OpenApiConfigurationOptions) .BuildOAuth2RedirectAsync(context.PackageAssembly) .RenderOAuth2RedirectAsync("oauth2-redirect.html", context.GetDocumentAuthLevel(), context.GetSwaggerAuthKey()) .ConfigureAwait(false); diff --git a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Extensions/HttpRequestDataExtensionsTests.cs b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Extensions/HttpRequestDataExtensionsTests.cs index a06c5965..70d90d73 100644 --- a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Extensions/HttpRequestDataExtensionsTests.cs +++ b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Extensions/HttpRequestDataExtensionsTests.cs @@ -1,7 +1,8 @@ using System; - +using System.Collections.Generic; +using System.Linq; using FluentAssertions; - +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Extensions; using Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests.Fakes; using Microsoft.Azure.Functions.Worker.Http; @@ -14,6 +15,128 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests.Extensions [TestClass] public class HttpRequestDataExtensionsTests { + [TestMethod] + public void Given_Null_When_Headers_Invoked_Then_It_Should_Throw_Exception() + { + Action action = () => OpenApiHttpRequestDataExtensions.Headers(null); + + action.Should().Throw(); + } + + [TestMethod] + public void Given_NullHeader_When_Headers_Invoked_Then_It_Should_Return_Result() + { + var context = new Mock(); + + var baseHost = "localhost"; + var uri = Uri.TryCreate($"http://{baseHost}", UriKind.Absolute, out var tried) ? tried : null; + + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: null); + + var result = OpenApiHttpRequestDataExtensions.Headers(req); + + result.Count.Should().Be(0); + } + + [TestMethod] + public void Given_NoHeader_When_Headers_Invoked_Then_It_Should_Return_Result() + { + var context = new Mock(); + + var baseHost = "localhost"; + var uri = Uri.TryCreate($"http://{baseHost}", UriKind.Absolute, out var tried) ? tried : null; + + var headers = new Dictionary(); + + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: headers); + + var result = OpenApiHttpRequestDataExtensions.Headers(req); + + result.Count.Should().Be(0); + } + + [DataTestMethod] + [DataRow("hello=world", "hello")] + [DataRow("hello=world&lorem=ipsum", "hello", "lorem")] + public void Given_Headers_When_Headers_Invoked_Then_It_Should_Return_Result(string headerstring, params string[] keys) + { + var context = new Mock(); + + var baseHost = "localhost"; + var uri = Uri.TryCreate($"http://{baseHost}", UriKind.Absolute, out var tried) ? tried : null; + + var kvps = headerstring.Split('&').ToDictionary(p => p.Split('=').First(), p => p.Split('=').Last()); + var headers = new Dictionary(kvps); + + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: headers); + + var result = OpenApiHttpRequestDataExtensions.Headers(req); + + result.Count.Should().Be(keys.Length); + result.Keys.Should().Contain(keys); + } + + [TestMethod] + public void Given_Null_When_Header_Invoked_Then_It_Should_Throw_Exception() + { + Action action = () => OpenApiHttpRequestDataExtensions.Header(null, null); + + action.Should().Throw(); + } + + [DataTestMethod] + [DataRow("hello=world", "hello", "world")] + [DataRow("hello=world&lorem=ipsum", "hello", "world")] + [DataRow("hello=world&lorem=ipsum", "lorem", "ipsum")] + public void Given_Headers_When_Header_Invoked_Then_It_Should_Return_Result(string headerstring, string key, string expected) + { + var context = new Mock(); + + var baseHost = "localhost"; + var uri = Uri.TryCreate($"http://{baseHost}", UriKind.Absolute, out var tried) ? tried : null; + + var kvps = headerstring.Split('&').ToDictionary(p => p.Split('=').First(), p => p.Split('=').Last()); + var headers = new Dictionary(kvps); + + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: headers); + + var result = (string) OpenApiHttpRequestDataExtensions.Header(req, key); + + result.Should().Be(expected); + } + + [TestMethod] + public void Given_NullHeader_When_Header_Invoked_Then_It_Should_Return_Result() + { + var context = new Mock(); + + var baseHost = "localhost"; + var uri = Uri.TryCreate($"http://{baseHost}", UriKind.Absolute, out var tried) ? tried : null; + + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: null); + + var result = (string) OpenApiHttpRequestDataExtensions.Header(req, "hello"); + + result.Should().BeNull(); + } + + [TestMethod] + public void Given_NoHeader_When_Header_Invoked_Then_It_Should_Return_Result() + { + var context = new Mock(); + + var baseHost = "localhost"; + var uri = Uri.TryCreate($"http://{baseHost}", UriKind.Absolute, out var tried) ? tried : null; + + var headers = new Dictionary(); + + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: headers); + + var result = (string) OpenApiHttpRequestDataExtensions.Query(req, "hello"); + + result.Should().BeNull(); + } + [TestMethod] public void Given_Null_When_Queries_Invoked_Then_It_Should_Throw_Exception() { @@ -30,7 +153,7 @@ public void Given_NullQuerystring_When_Queries_Invoked_Then_It_Should_Return_Res var baseHost = "localhost"; var uri = Uri.TryCreate($"http://{baseHost}", UriKind.Absolute, out var tried) ? tried : null; - var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, body: null); + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: null); var result = OpenApiHttpRequestDataExtensions.Queries(req); @@ -47,7 +170,7 @@ public void Given_NoQuerystring_When_Queries_Invoked_Then_It_Should_Return_Resul var baseHost = "localhost"; var uri = Uri.TryCreate($"http://{baseHost}?{querystring}", UriKind.Absolute, out var tried) ? tried : null; - var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, body: null); + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: null); var result = OpenApiHttpRequestDataExtensions.Queries(req); @@ -64,7 +187,7 @@ public void Given_Querystring_When_Queries_Invoked_Then_It_Should_Return_Result( var baseHost = "localhost"; var uri = Uri.TryCreate($"http://{baseHost}?{querystring}", UriKind.Absolute, out var tried) ? tried : null; - var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, body: null); + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: null); var result = OpenApiHttpRequestDataExtensions.Queries(req); @@ -91,7 +214,7 @@ public void Given_Querystring_When_Query_Invoked_Then_It_Should_Return_Result(st var baseHost = "localhost"; var uri = Uri.TryCreate($"http://{baseHost}?{querystring}", UriKind.Absolute, out var tried) ? tried : null; - var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, body: null); + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: null); var result = (string) OpenApiHttpRequestDataExtensions.Query(req, key); @@ -106,7 +229,7 @@ public void Given_NullQuerystring_When_Query_Invoked_Then_It_Should_Return_Resul var baseHost = "localhost"; var uri = Uri.TryCreate($"http://{baseHost}", UriKind.Absolute, out var tried) ? tried : null; - var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, body: null); + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: null); var result = (string) OpenApiHttpRequestDataExtensions.Query(req, "hello"); @@ -123,7 +246,7 @@ public void Given_NoQuerystring_When_Query_Invoked_Then_It_Should_Return_Result( var baseHost = "localhost"; var uri = Uri.TryCreate($"http://{baseHost}?{querystring}", UriKind.Absolute, out var tried) ? tried : null; - var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, body: null); + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers: null); var result = (string) OpenApiHttpRequestDataExtensions.Query(req, "hello"); diff --git a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Fakes/FakeHttpRequestData.cs b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Fakes/FakeHttpRequestData.cs index 21469656..c20532dd 100644 --- a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Fakes/FakeHttpRequestData.cs +++ b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Fakes/FakeHttpRequestData.cs @@ -2,22 +2,24 @@ using System.Collections.Generic; using System.IO; using System.Security.Claims; - +using Microsoft.AspNetCore.Http; using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Primitives; namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests.Fakes { public class FakeHttpRequestData : HttpRequestData { - public FakeHttpRequestData(FunctionContext functionContext, Uri uri, Stream body) : base(functionContext) + public FakeHttpRequestData(FunctionContext functionContext, Uri uri, Dictionary headers, Stream body = null) : base(functionContext) { this.Url = uri; this.Body = body; + this.Headers = new HttpHeadersCollection(headers ?? new Dictionary()); } public override Stream Body { get; } - public override HttpHeadersCollection Headers => throw new NotImplementedException(); + public override HttpHeadersCollection Headers { get; } public override IReadOnlyCollection Cookies => throw new NotImplementedException(); diff --git a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Fakes/FakeOpenApiHttpTriggerAuthorization.cs b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Fakes/FakeOpenApiHttpTriggerAuthorization.cs new file mode 100644 index 00000000..77ac1522 --- /dev/null +++ b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/Fakes/FakeOpenApiHttpTriggerAuthorization.cs @@ -0,0 +1,27 @@ +using System.Net; +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; + +namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests.Fakes +{ + public class FakeOpenApiHttpTriggerAuthorization : DefaultOpenApiHttpTriggerAuthorization + { + public const HttpStatusCode StatusCode = HttpStatusCode.Redirect; + public const string ContentType = "text/html"; + public const string Payload = ""; + + public override async Task AuthorizeAsync(IHttpRequestDataObject req) + { + var result = new OpenApiAuthorizationResult() + { + StatusCode = StatusCode, + ContentType = ContentType, + Payload = Payload, + }; + + return await Task.FromResult(result).ConfigureAwait(false); + } + } +} diff --git a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/HttpRequestObjectTests.cs b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/HttpRequestObjectTests.cs index 01e255ed..9618629a 100644 --- a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/HttpRequestObjectTests.cs +++ b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/HttpRequestObjectTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -36,15 +37,19 @@ public void Given_Parameter_When_Instantiated_Then_It_Should_Return_Result(strin var ports = new[] { 80, 443 }; var baseHost = $"{hostname}{(ports.Contains(port) ? string.Empty : $":{port}")}"; var uri = Uri.TryCreate($"{scheme}://{baseHost}?{key}={value}", UriKind.Absolute, out var tried) ? tried : null; + + var headers = new Dictionary() { { key, value } }; + var bytes = Encoding.UTF8.GetBytes(payload); var body = new MemoryStream(bytes); - var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, body); + var req = (HttpRequestData) new FakeHttpRequestData(context.Object, uri, headers, body); var result = new HttpRequestObject(req); result.Scheme.Should().Be(scheme); result.Host.Value.Should().Be(baseHost); + result.Headers.Should().ContainKey(key); result.Query.Should().ContainKey(key); ((string) result.Query[key]).Should().Be(value); (new StreamReader(result.Body)).ReadToEnd().Should().Be(payload); diff --git a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs index 3df8352f..3c6e1376 100644 --- a/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs +++ b/test/Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs @@ -7,10 +7,14 @@ using FluentAssertions; using Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Functions; +using Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests.Fakes; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; using Microsoft.OpenApi; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + namespace Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Tests { [TestClass] @@ -71,7 +75,6 @@ public async Task Given_Type_When_Initiated_Then_It_Should_Return_PackageAssembl .PackageAssembly; assembly.DefinedTypes.Should().Contain(typeof(ISwaggerUI).GetTypeInfo()); - assembly.DefinedTypes.Should().NotContain(typeof(IOpenApiHttpTriggerContext).GetTypeInfo()); } [TestMethod] @@ -113,6 +116,21 @@ public async Task Given_Type_When_Initiated_Then_It_Should_Return_HttpSettings() } + [TestMethod] + public async Task Given_Authorization_When_AuthorizeAsync_Invoked_Then_It_Should_Return_Result() + { + var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName; + var req = new Mock(); + var context = new OpenApiHttpTriggerContext(); + + var result = await context.SetApplicationAssemblyAsync(location, false) + .AuthorizeAsync(req.Object); + + result.StatusCode.Should().Be(FakeOpenApiHttpTriggerAuthorization.StatusCode); + result.ContentType.Should().Be(FakeOpenApiHttpTriggerAuthorization.ContentType); + result.Payload.Should().Be(FakeOpenApiHttpTriggerAuthorization.Payload); + } + [DataTestMethod] [DataRow("v2", OpenApiSpecVersion.OpenApi2_0)] [DataRow("v3", OpenApiSpecVersion.OpenApi3_0)] diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/DefaultOpenApiHttpTriggerAuthorizationTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/DefaultOpenApiHttpTriggerAuthorizationTests.cs new file mode 100644 index 00000000..aa6b3f19 --- /dev/null +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Configurations/DefaultOpenApiHttpTriggerAuthorizationTests.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; + +using FluentAssertions; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Configurations +{ + [TestClass] + public class DefaultOpenApiHttpTriggerAuthorizationTests + { + [TestMethod] + public async Task Given_Type_When_Instantiated_Then_Properties_Should_Return_Null() + { + var req = new Mock(); + var auth = new DefaultOpenApiHttpTriggerAuthorization(); + + var result = await auth.AuthorizeAsync(req.Object).ConfigureAwait(false); + + result.Should().BeNull(); + } + } +} diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Extensions/OpenApiHttpTriggerContextExtensionsTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Extensions/OpenApiHttpTriggerContextExtensionsTests.cs new file mode 100644 index 00000000..82699c17 --- /dev/null +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests/Extensions/OpenApiHttpTriggerContextExtensionsTests.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; + +using FluentAssertions; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Tests.Extensions +{ + [TestClass] + public class OpenApiHttpTriggerContextExtensionsTests + { + [TestMethod] + public void Given_Null_Parameter_When_AuthorizeAsync_Invoked_Then_It_Should_Throw_Exception() + { + var context = new Mock(); + + Func func = async () => await OpenApiHttpTriggerContextExtensions.AuthorizeAsync(null, null).ConfigureAwait(false); + func.Should().ThrowAsync(); + + func = async () => await OpenApiHttpTriggerContextExtensions.AuthorizeAsync(Task.FromResult(context.Object), null).ConfigureAwait(false); + func.Should().ThrowAsync(); + } + + [TestMethod] + public async Task Given_Parameter_When_AuthorizeAsync_Invoked_Then_It_Should_Return_Result() + { + var context = new Mock(); + context.Setup(p => p.AuthorizeAsync(It.IsAny())).ReturnsAsync(default(OpenApiAuthorizationResult)); + + var req = new Mock(); + + var result = await OpenApiHttpTriggerContextExtensions.AuthorizeAsync(Task.FromResult(context.Object), req.Object).ConfigureAwait(false); + + result.Should().BeNull(); + } + } +} diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Fakes/FakeOpenApiHttpTriggerAuthorization.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Fakes/FakeOpenApiHttpTriggerAuthorization.cs new file mode 100644 index 00000000..a8e9aba3 --- /dev/null +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/Fakes/FakeOpenApiHttpTriggerAuthorization.cs @@ -0,0 +1,27 @@ +using System.Net; +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.Fakes +{ + public class FakeOpenApiHttpTriggerAuthorization : DefaultOpenApiHttpTriggerAuthorization + { + public const HttpStatusCode StatusCode = HttpStatusCode.Redirect; + public const string ContentType = "text/html"; + public const string Payload = ""; + + public override async Task AuthorizeAsync(IHttpRequestDataObject req) + { + var result = new OpenApiAuthorizationResult() + { + StatusCode = StatusCode, + ContentType = ContentType, + Payload = Payload, + }; + + return await Task.FromResult(result).ConfigureAwait(false); + } + } +} diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/HttpRequestObjectTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/HttpRequestObjectTests.cs index 8de69086..772c43ed 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/HttpRequestObjectTests.cs +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/HttpRequestObjectTests.cs @@ -42,6 +42,9 @@ public void Given_Parameter_When_Instantiated_Then_It_Should_Return_Result(strin req.SetupGet(p => p.Host).Returns(hoststring); var dict = new Dictionary() { { key, new StringValues(value) } }; + var header = new HeaderDictionary(dict); + req.SetupGet(p => p.Headers).Returns(header); + var query = new QueryCollection(dict); req.SetupGet(p => p.Query).Returns(query); @@ -53,6 +56,7 @@ public void Given_Parameter_When_Instantiated_Then_It_Should_Return_Result(strin result.Scheme.Should().Be(scheme); result.Host.Value.Should().Be(baseHost); + result.Headers.Should().ContainKey(key); result.Query.Should().ContainKey(key); ((string) result.Query[key]).Should().Be(value); (new StreamReader(result.Body)).ReadToEnd().Should().Be(payload); diff --git a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs index c4950ee7..285b9bfb 100644 --- a/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs +++ b/test/Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests/OpenApiHttpTriggerContextTests.cs @@ -7,9 +7,13 @@ using FluentAssertions; using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests.Fakes; using Microsoft.OpenApi; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Tests { [TestClass] @@ -70,7 +74,6 @@ public async Task Given_Type_When_Initiated_Then_It_Should_Return_PackageAssembl .PackageAssembly; assembly.DefinedTypes.Should().Contain(typeof(ISwaggerUI).GetTypeInfo()); - assembly.DefinedTypes.Should().NotContain(typeof(IOpenApiHttpTriggerContext).GetTypeInfo()); } [TestMethod] @@ -109,7 +112,21 @@ public async Task Given_Type_When_Initiated_Then_It_Should_Return_HttpSettings() .HttpSettings; settings.RoutePrefix.Should().Be("api"); + } + + [TestMethod] + public async Task Given_Authorization_When_AuthorizeAsync_Invoked_Then_It_Should_Return_Result() + { + var location = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName; + var req = new Mock(); + var context = new OpenApiHttpTriggerContext(); + + var result = await context.SetApplicationAssemblyAsync(location, false) + .AuthorizeAsync(req.Object); + result.StatusCode.Should().Be(FakeOpenApiHttpTriggerAuthorization.StatusCode); + result.ContentType.Should().Be(FakeOpenApiHttpTriggerAuthorization.ContentType); + result.Payload.Should().Be(FakeOpenApiHttpTriggerAuthorization.Payload); } [DataTestMethod]