From 9ccf87f7d25c93d1945ba7794897b18991fdba0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:52:09 +0000 Subject: [PATCH 1/3] Initial plan From 8f85716347dddf2c8b42c870c4f313ffcdf5dbb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:58:12 +0000 Subject: [PATCH 2/3] fix openapi bearer auth scheme --- .../Services/OpenAPI/OpenApiDocumentor.cs | 42 ++++++++-- .../ParameterValidationTests.cs | 11 +-- .../OpenApiDocumentor/SecuritySchemeTests.cs | 82 +++++++++++++++++++ 3 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 src/Service.Tests/OpenApiDocumentor/SecuritySchemeTests.cs diff --git a/src/Core/Services/OpenAPI/OpenApiDocumentor.cs b/src/Core/Services/OpenAPI/OpenApiDocumentor.cs index 979da52eb6..1e6d186608 100644 --- a/src/Core/Services/OpenAPI/OpenApiDocumentor.cs +++ b/src/Core/Services/OpenAPI/OpenApiDocumentor.cs @@ -52,6 +52,7 @@ public class OpenApiDocumentor : IOpenApiDocumentor public const string SP_RESPONSE_SUFFIX = "_sp_response"; public const string SCHEMA_OBJECT_TYPE = "object"; public const string RESPONSE_ARRAY_PROPERTY = "array"; + public const string BEARER_AUTH_SECURITY_SCHEME = "BearerAuth"; // Routing constant public const string OPENAPI_ROUTE = "openapi"; @@ -224,7 +225,7 @@ private OpenApiDocument BuildOpenApiDocument(RuntimeConfig runtimeConfig, string }); } - return new OpenApiDocument() + OpenApiDocument document = new() { Info = new OpenApiInfo { @@ -239,6 +240,9 @@ private OpenApiDocument BuildOpenApiDocument(RuntimeConfig runtimeConfig, string Components = components, Tags = globalTagsDict.Values.ToList() }; + + AddAuthenticationSecurity(document, runtimeConfig.Runtime?.Host?.Authentication); + return document; } /// @@ -650,7 +654,7 @@ private OpenApiOperation CreateBaseOperation(string description, List /// OpenApi operation. - private static void AddCustomHeadersToOperation(OpenApiOperation operation) + internal static void AddCustomHeadersToOperation(OpenApiOperation operation) { OpenApiSchema stringParamSchema = new() { @@ -666,16 +670,38 @@ private static void AddCustomHeadersToOperation(OpenApiOperation operation) Schema = stringParamSchema }; operation.Parameters.Add(paramForClientHeader); + } - // Add parameter for Authorization header. - OpenApiParameter paramForAuthHeader = new() + internal static void AddAuthenticationSecurity(OpenApiDocument document, AuthenticationOptions? authenticationOptions) + { + if (authenticationOptions is null || authenticationOptions.IsUnauthenticatedAuthenticationProvider()) { - Required = false, + return; + } + + document.Components ??= new OpenApiComponents(); + document.Components.SecuritySchemes ??= new Dictionary(); + document.Components.SecuritySchemes[BEARER_AUTH_SECURITY_SCHEME] = new OpenApiSecurityScheme + { + Type = SecuritySchemeType.Http, + Scheme = "bearer", + BearerFormat = "JWT", In = ParameterLocation.Header, - Name = "Authorization", - Schema = stringParamSchema + Name = AuthorizationResolver.AUTHORIZATION_HEADER }; - operation.Parameters.Add(paramForAuthHeader); + + document.SecurityRequirements ??= new List(); + document.SecurityRequirements.Add(new OpenApiSecurityRequirement + { + [new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = BEARER_AUTH_SECURITY_SCHEME + } + }] = [] + }); } /// diff --git a/src/Service.Tests/OpenApiDocumentor/ParameterValidationTests.cs b/src/Service.Tests/OpenApiDocumentor/ParameterValidationTests.cs index 551a7b0132..94ee35cf1f 100644 --- a/src/Service.Tests/OpenApiDocumentor/ParameterValidationTests.cs +++ b/src/Service.Tests/OpenApiDocumentor/ParameterValidationTests.cs @@ -259,8 +259,7 @@ private async static Task GenerateOpenApiDocumentForGivenEntity } /// - /// Test to validate that the custom header parameters are present for each of the operation irrespective of the operation and the type - /// of the entity. + /// Test to validate that supported custom header parameters are present for each operation irrespective of the operation and entity type. /// /// Name of the entity. /// Name of the database object backing the entity. @@ -281,12 +280,10 @@ public async Task ValidateHeaderParametersForEntity(string entityName, string ob { foreach ((OperationType operationType, OpenApiOperation operation) in pathItem.Operations) { - // Assert presence of Authorization header and the expected parameter properties for the header parameter. - Assert.IsTrue(operation.Parameters.Any( + // Assert absence of Authorization header because OpenAPI security schemes handle bearer auth. + Assert.IsFalse(operation.Parameters.Any( param => param.In is ParameterLocation.Header && - AuthorizationResolver.AUTHORIZATION_HEADER.Equals(param.Name) && - JsonDataType.String.ToString().ToLower().Equals(param.Schema.Type) && - param.Required is false)); + AuthorizationResolver.AUTHORIZATION_HEADER.Equals(param.Name))); // Assert presence of X-MS-API-ROLE header and the expected parameter properties for the header parameter. Assert.IsTrue(operation.Parameters.Any( diff --git a/src/Service.Tests/OpenApiDocumentor/SecuritySchemeTests.cs b/src/Service.Tests/OpenApiDocumentor/SecuritySchemeTests.cs new file mode 100644 index 0000000000..38a687fc76 --- /dev/null +++ b/src/Service.Tests/OpenApiDocumentor/SecuritySchemeTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using Azure.DataApiBuilder.Config.ObjectModel; +using Azure.DataApiBuilder.Core.Authorization; +using Azure.DataApiBuilder.Core.Services; +using Microsoft.OpenApi.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Azure.DataApiBuilder.Service.Tests.OpenApiIntegration; + +[TestClass] +public class SecuritySchemeTests +{ + [TestMethod] + public void AddCustomHeadersToOperation_AddsRoleHeaderButNotAuthorizationHeader() + { + OpenApiOperation operation = new() + { + Parameters = new List() + }; + + OpenApiDocumentor.AddCustomHeadersToOperation(operation); + + Assert.IsFalse(operation.Parameters.Any(param => + param.In is ParameterLocation.Header && + AuthorizationResolver.AUTHORIZATION_HEADER.Equals(param.Name))); + + Assert.IsTrue(operation.Parameters.Any(param => + param.In is ParameterLocation.Header && + AuthorizationResolver.CLIENT_ROLE_HEADER.Equals(param.Name) && + param.Schema?.Type.Equals("string") is true && + param.Required is false)); + } + + [DataTestMethod] + [DataRow("AppService")] + [DataRow("StaticWebApps")] + [DataRow("Simulator")] + [DataRow("CustomJwt")] + public void AddAuthenticationSecurity_AddsBearerSecuritySchemeForAuthenticatedProviders(string provider) + { + OpenApiDocument document = new() + { + Components = new OpenApiComponents() + }; + + OpenApiDocumentor.AddAuthenticationSecurity(document, new AuthenticationOptions(Provider: provider)); + + Assert.IsNotNull(document.Components.SecuritySchemes); + Assert.AreEqual(1, document.Components.SecuritySchemes.Count); + + OpenApiSecurityScheme securityScheme = document.Components.SecuritySchemes[OpenApiDocumentor.BEARER_AUTH_SECURITY_SCHEME]; + Assert.AreEqual(SecuritySchemeType.Http, securityScheme.Type); + Assert.AreEqual("bearer", securityScheme.Scheme); + Assert.AreEqual("JWT", securityScheme.BearerFormat); + Assert.AreEqual(ParameterLocation.Header, securityScheme.In); + Assert.AreEqual(AuthorizationResolver.AUTHORIZATION_HEADER, securityScheme.Name); + + Assert.IsNotNull(document.SecurityRequirements); + Assert.AreEqual(1, document.SecurityRequirements.Count); + Assert.IsTrue(document.SecurityRequirements.Single().Keys.Any(scheme => + scheme.Reference?.Type is ReferenceType.SecurityScheme && + OpenApiDocumentor.BEARER_AUTH_SECURITY_SCHEME.Equals(scheme.Reference.Id))); + } + + [TestMethod] + public void AddAuthenticationSecurity_DoesNotAddBearerSecuritySchemeForUnauthenticatedProvider() + { + OpenApiDocument document = new() + { + Components = new OpenApiComponents() + }; + + OpenApiDocumentor.AddAuthenticationSecurity(document, new AuthenticationOptions(Provider: AuthenticationOptions.UNAUTHENTICATED_AUTHENTICATION)); + + Assert.IsTrue(document.Components.SecuritySchemes is null || document.Components.SecuritySchemes.Count is 0); + Assert.IsTrue(document.SecurityRequirements is null || document.SecurityRequirements.Count is 0); + } +} From 23982ad3fa53451955354e062d64e2b056abd576 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Jul 2026 00:02:32 +0000 Subject: [PATCH 3/3] test openapi auth security behavior --- .../OpenApiDocumentor/ParameterValidationTests.cs | 2 +- .../OpenApiDocumentor/SecuritySchemeTests.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Service.Tests/OpenApiDocumentor/ParameterValidationTests.cs b/src/Service.Tests/OpenApiDocumentor/ParameterValidationTests.cs index 94ee35cf1f..795b6d6311 100644 --- a/src/Service.Tests/OpenApiDocumentor/ParameterValidationTests.cs +++ b/src/Service.Tests/OpenApiDocumentor/ParameterValidationTests.cs @@ -259,7 +259,7 @@ private async static Task GenerateOpenApiDocumentForGivenEntity } /// - /// Test to validate that supported custom header parameters are present for each operation irrespective of the operation and entity type. + /// Test to validate that supported custom header parameters are present for each operation regardless of operation and entity type. /// /// Name of the entity. /// Name of the database object backing the entity. diff --git a/src/Service.Tests/OpenApiDocumentor/SecuritySchemeTests.cs b/src/Service.Tests/OpenApiDocumentor/SecuritySchemeTests.cs index 38a687fc76..5a607546dc 100644 --- a/src/Service.Tests/OpenApiDocumentor/SecuritySchemeTests.cs +++ b/src/Service.Tests/OpenApiDocumentor/SecuritySchemeTests.cs @@ -79,4 +79,18 @@ public void AddAuthenticationSecurity_DoesNotAddBearerSecuritySchemeForUnauthent Assert.IsTrue(document.Components.SecuritySchemes is null || document.Components.SecuritySchemes.Count is 0); Assert.IsTrue(document.SecurityRequirements is null || document.SecurityRequirements.Count is 0); } + + [TestMethod] + public void AddAuthenticationSecurity_DoesNotAddBearerSecuritySchemeWhenAuthenticationOptionsAreNull() + { + OpenApiDocument document = new() + { + Components = new OpenApiComponents() + }; + + OpenApiDocumentor.AddAuthenticationSecurity(document, authenticationOptions: null); + + Assert.IsTrue(document.Components.SecuritySchemes is null || document.Components.SecuritySchemes.Count is 0); + Assert.IsTrue(document.SecurityRequirements is null || document.SecurityRequirements.Count is 0); + } }