From 5ea3fe37601400085476ef4f6c9e5c728d63d09b Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 14 Jul 2026 15:07:00 -0700 Subject: [PATCH 1/8] Bug fix --- src/Core/Resolvers/MsSqlQueryExecutor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Core/Resolvers/MsSqlQueryExecutor.cs b/src/Core/Resolvers/MsSqlQueryExecutor.cs index b4f84757b3..4a2deb4394 100644 --- a/src/Core/Resolvers/MsSqlQueryExecutor.cs +++ b/src/Core/Resolvers/MsSqlQueryExecutor.cs @@ -516,6 +516,7 @@ public override string GetSessionParamsQuery(HttpContext? httpContext, IDictiona // Counter to generate different param name for each of the sessionParam. IncrementingInteger counter = new(); + const string SESSION_KEY_NAME = $"{BaseQueryStructure.PARAM_NAME_PREFIX}session_key"; const string SESSION_PARAM_NAME = $"{BaseQueryStructure.PARAM_NAME_PREFIX}session_param"; StringBuilder sessionMapQuery = new(); @@ -528,10 +529,12 @@ public override string GetSessionParamsQuery(HttpContext? httpContext, IDictiona foreach ((string claimType, string claimValue) in sessionParams) { + string keyName = $"{SESSION_KEY_NAME}{counter.Current()}"; string paramName = $"{SESSION_PARAM_NAME}{counter.Next()}"; + parameters.Add(keyName, new(claimType)); parameters.Add(paramName, new(claimValue)); // Append statement to set read only param value - can be set only once for a connection. - string statementToSetReadOnlyParam = "EXEC sp_set_session_context " + $"'{claimType}', " + paramName + ", @read_only = 0;"; + string statementToSetReadOnlyParam = "EXEC sp_set_session_context " + keyName + ", " + paramName + ", @read_only = 0;"; sessionMapQuery = sessionMapQuery.Append(statementToSetReadOnlyParam); } } From c233df2d097a1dfbf9d6939a9eb13f50589daed5 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 14 Jul 2026 17:14:52 -0700 Subject: [PATCH 2/8] Add test --- .../EasyAuthAuthenticationUnitTests.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs index 35ef925dab..5a84f746dc 100644 --- a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs +++ b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs @@ -2,9 +2,11 @@ // Licensed under the MIT License. #nullable enable +using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Security.Claims; using System.Threading.Tasks; using Azure.DataApiBuilder.Config.ObjectModel; @@ -344,6 +346,50 @@ await SendRequestAndGetHttpContextState( ignoreCase: true); } + /// + /// Tests we ensure that an invalid query in the EasyAuth header does not result in a successful request. + /// + [TestMethod] + public async Task TestInvalidQueryInHeader() + { + string header = @" + { + ""auth_typ"":""aad"", + ""claims"":[ + { + ""typ"":""x', N'v'; + SET IDENTITY_INSERT authors ON; + INSERT INTO authors (id, name, birthdate) VALUES (10001, 'Hidden Author', '2001-01-01'); + SET IDENTITY_INSERT authors OFF; + SELECT 1 AS inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER; + --"", + ""val"":""x"" + } + ] + }"; + + string generatedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(header)); + HttpContext postMiddlewareContext = + await SendRequestAndGetHttpContextState( + generatedToken, + EasyAuthType.StaticWebApps, + clientRoleHeader: "authenticated"); + Assert.AreEqual(expected: (int)HttpStatusCode.OK, actual: postMiddlewareContext.Response.StatusCode); + + using IHost host = await WebHostBuilderHelper.CreateWebHost(EasyAuthType.StaticWebApps.ToString(), false); + TestServer server = host.GetTestServer(); + HttpClient client = server.CreateClient(); + + HttpRequestMessage request = new(HttpMethod.Get, $"api/Author/id/10001"); + HttpResponseMessage response = await client.SendAsync(request); + string responseBody = await response.Content.ReadAsStringAsync(); + + Assert.IsFalse(responseBody.Contains($"\"id\":10001"), + "The GET request should not return the invalid row."); + Assert.IsFalse(responseBody.Contains("Hidden Author"), + "The GET request should not return any information related to the invalid row."); + } + /// /// - Ensures an invalid EasyAuth header payload results in HTTP 401 Unauthorized response /// A correctly configured EasyAuth environment guarantees an EasyAuth payload for authenticated requests. From cd5b9d781e91574d96199cdfc15850657d585a88 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 15 Jul 2026 09:45:55 -0700 Subject: [PATCH 3/8] Fix test --- .../EasyAuthAuthenticationUnitTests.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs index 5a84f746dc..93419b0a1a 100644 --- a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs +++ b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs @@ -327,7 +327,6 @@ public async Task TestValidStaticWebAppsEasyAuthTokenWithAnonymousRoleOnly() DisplayName = "Anonymous role - X-MS-API-ROLE is not honored")] [DataRow(true, "author", DisplayName = "Authenticated role - existing X-MS-API-ROLE is honored")] - [TestMethod] public async Task TestClientRoleHeaderPresence(bool addAuthenticated, string clientRoleHeader) { string generatedToken = AuthTestHelper.CreateStaticWebAppsEasyAuthToken(addAuthenticated); @@ -357,15 +356,11 @@ public async Task TestInvalidQueryInHeader() ""auth_typ"":""aad"", ""claims"":[ { - ""typ"":""x', N'v'; - SET IDENTITY_INSERT authors ON; - INSERT INTO authors (id, name, birthdate) VALUES (10001, 'Hidden Author', '2001-01-01'); - SET IDENTITY_INSERT authors OFF; - SELECT 1 AS inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER; - --"", + ""typ"":""x', N'v';SET IDENTITY_INSERT authors ON;INSERT INTO authors (id, name, birthdate) VALUES (10001, 'Hidden Author', '2001-01-01');SET IDENTITY_INSERT authors OFF;SELECT 1 AS inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER;--"", ""val"":""x"" } - ] + ], + ""UserRoles"":[""anonymous""] }"; string generatedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(header)); @@ -373,7 +368,7 @@ public async Task TestInvalidQueryInHeader() await SendRequestAndGetHttpContextState( generatedToken, EasyAuthType.StaticWebApps, - clientRoleHeader: "authenticated"); + clientRoleHeader: "anonymous"); Assert.AreEqual(expected: (int)HttpStatusCode.OK, actual: postMiddlewareContext.Response.StatusCode); using IHost host = await WebHostBuilderHelper.CreateWebHost(EasyAuthType.StaticWebApps.ToString(), false); From 584ce71a8874beb6b913aba4d3cd49002a730e03 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 15 Jul 2026 13:57:24 -0700 Subject: [PATCH 4/8] Fix test --- src/Core/Resolvers/MsSqlQueryExecutor.cs | 4 +- .../EasyAuthAuthenticationUnitTests.cs | 61 +++++++++++++++---- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/Core/Resolvers/MsSqlQueryExecutor.cs b/src/Core/Resolvers/MsSqlQueryExecutor.cs index 4a2deb4394..2078b4f1c5 100644 --- a/src/Core/Resolvers/MsSqlQueryExecutor.cs +++ b/src/Core/Resolvers/MsSqlQueryExecutor.cs @@ -530,9 +530,11 @@ public override string GetSessionParamsQuery(HttpContext? httpContext, IDictiona foreach ((string claimType, string claimValue) in sessionParams) { string keyName = $"{SESSION_KEY_NAME}{counter.Current()}"; - string paramName = $"{SESSION_PARAM_NAME}{counter.Next()}"; parameters.Add(keyName, new(claimType)); + + string paramName = $"{SESSION_PARAM_NAME}{counter.Next()}"; parameters.Add(paramName, new(claimValue)); + // Append statement to set read only param value - can be set only once for a connection. string statementToSetReadOnlyParam = "EXEC sp_set_session_context " + keyName + ", " + paramName + ", @read_only = 0;"; sessionMapQuery = sessionMapQuery.Append(statementToSetReadOnlyParam); diff --git a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs index 93419b0a1a..40ea59fa1a 100644 --- a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs +++ b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs @@ -3,6 +3,7 @@ #nullable enable using System; +using System.IO; using System.Collections.Generic; using System.Linq; using System.Net; @@ -12,6 +13,7 @@ using Azure.DataApiBuilder.Config.ObjectModel; using Azure.DataApiBuilder.Core.AuthenticationHelpers; using Azure.DataApiBuilder.Core.Authorization; +using Azure.DataApiBuilder.Core.Configurations; using Azure.DataApiBuilder.Service.Tests.Authentication.Helpers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; @@ -356,24 +358,27 @@ public async Task TestInvalidQueryInHeader() ""auth_typ"":""aad"", ""claims"":[ { - ""typ"":""x', N'v';SET IDENTITY_INSERT authors ON;INSERT INTO authors (id, name, birthdate) VALUES (10001, 'Hidden Author', '2001-01-01');SET IDENTITY_INSERT authors OFF;SELECT 1 AS inserted FOR JSON PATH, WITHOUT_ARRAY_WRAPPER;--"", + ""typ"":""x', N'v';INSERT INTO authors (id, name, birthdate) VALUES (10001, 'Hidden Author', '2001-01-01');--"", ""val"":""x"" } ], - ""UserRoles"":[""anonymous""] + ""UserRoles"":[""authenticated""] }"; string generatedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(header)); - HttpContext postMiddlewareContext = - await SendRequestAndGetHttpContextState( - generatedToken, - EasyAuthType.StaticWebApps, - clientRoleHeader: "anonymous"); - Assert.AreEqual(expected: (int)HttpStatusCode.OK, actual: postMiddlewareContext.Response.StatusCode); - using IHost host = await WebHostBuilderHelper.CreateWebHost(EasyAuthType.StaticWebApps.ToString(), false); - TestServer server = host.GetTestServer(); - HttpClient client = server.CreateClient(); + const string SESSION_CONFIG = $"session-context-config.{TestCategory.MSSQL}.json"; + SetupSessionContextConfig(SESSION_CONFIG); + + string[] args = [$"--ConfigFileName={SESSION_CONFIG}"]; + using TestServer server = new(Program.CreateWebHostBuilder(args)); + using HttpClient client = server.CreateClient(); + + HttpRequestMessage requestWithHeader = new(HttpMethod.Get, "api/Author"); + requestWithHeader.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, generatedToken); + requestWithHeader.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, "authenticated"); + HttpResponseMessage responseWithHeader = await client.SendAsync(requestWithHeader); + Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseWithHeader.StatusCode); HttpRequestMessage request = new(HttpMethod.Get, $"api/Author/id/10001"); HttpResponseMessage response = await client.SendAsync(request); @@ -470,6 +475,40 @@ public static async Task SendRequestAndGetHttpContextState( context.Request.Scheme = "https"; }); } + + /// + /// Writes a MsSql runtime config that uses StaticWebApps EasyAuth and enables + /// set-session-context so requests exercise MsSqlQueryExecutor.GetSessionParamsQuery. + /// + private static void SetupSessionContextConfig(string configFileName) + { + TestHelper.SetupDatabaseEnvironment(TestCategory.MSSQL); + RuntimeConfigProvider configProvider = + TestHelper.GetRuntimeConfigProvider(TestHelper.GetRuntimeConfigLoader()); + RuntimeConfig config = configProvider.GetConfig(); + + RuntimeConfig updatedConfig = config with + { + DataSource = config.DataSource! with + { + Options = new Dictionary + { + // Matches MsSqlOptions.SetSessionContext (hyphenated naming policy). + { "set-session-context", true } + } + }, + Runtime = config.Runtime! with + { + Host = config.Runtime.Host! with + { + Authentication = new AuthenticationOptions( + Provider: EasyAuthType.AppService.ToString(), Jwt: null) + } + } + }; + + File.WriteAllText(configFileName, updatedConfig.ToJson()); + } #endregion } } From 85dfd9f96530850546479d2359ddcd23dda5d3fa Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 16 Jul 2026 09:31:59 -0700 Subject: [PATCH 5/8] Fix syntax error --- .../Authentication/EasyAuthAuthenticationUnitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs index 40ea59fa1a..0e8eb8f30e 100644 --- a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs +++ b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs @@ -3,8 +3,8 @@ #nullable enable using System; -using System.IO; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; From f8a568bdaa49b9db03541ca8e743e426f1069d8f Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 16 Jul 2026 16:21:20 -0700 Subject: [PATCH 6/8] Move test --- .../EasyAuthAuthenticationUnitTests.cs | 81 ------------------- .../UnitTests/SqlQueryExecutorUnitTests.cs | 74 +++++++++++++++++ 2 files changed, 74 insertions(+), 81 deletions(-) diff --git a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs index 0e8eb8f30e..4caf9a3a7a 100644 --- a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs +++ b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs @@ -2,18 +2,14 @@ // Licensed under the MIT License. #nullable enable -using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Security.Claims; using System.Threading.Tasks; using Azure.DataApiBuilder.Config.ObjectModel; using Azure.DataApiBuilder.Core.AuthenticationHelpers; using Azure.DataApiBuilder.Core.Authorization; -using Azure.DataApiBuilder.Core.Configurations; using Azure.DataApiBuilder.Service.Tests.Authentication.Helpers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; @@ -347,49 +343,6 @@ await SendRequestAndGetHttpContextState( ignoreCase: true); } - /// - /// Tests we ensure that an invalid query in the EasyAuth header does not result in a successful request. - /// - [TestMethod] - public async Task TestInvalidQueryInHeader() - { - string header = @" - { - ""auth_typ"":""aad"", - ""claims"":[ - { - ""typ"":""x', N'v';INSERT INTO authors (id, name, birthdate) VALUES (10001, 'Hidden Author', '2001-01-01');--"", - ""val"":""x"" - } - ], - ""UserRoles"":[""authenticated""] - }"; - - string generatedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(header)); - - const string SESSION_CONFIG = $"session-context-config.{TestCategory.MSSQL}.json"; - SetupSessionContextConfig(SESSION_CONFIG); - - string[] args = [$"--ConfigFileName={SESSION_CONFIG}"]; - using TestServer server = new(Program.CreateWebHostBuilder(args)); - using HttpClient client = server.CreateClient(); - - HttpRequestMessage requestWithHeader = new(HttpMethod.Get, "api/Author"); - requestWithHeader.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, generatedToken); - requestWithHeader.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, "authenticated"); - HttpResponseMessage responseWithHeader = await client.SendAsync(requestWithHeader); - Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseWithHeader.StatusCode); - - HttpRequestMessage request = new(HttpMethod.Get, $"api/Author/id/10001"); - HttpResponseMessage response = await client.SendAsync(request); - string responseBody = await response.Content.ReadAsStringAsync(); - - Assert.IsFalse(responseBody.Contains($"\"id\":10001"), - "The GET request should not return the invalid row."); - Assert.IsFalse(responseBody.Contains("Hidden Author"), - "The GET request should not return any information related to the invalid row."); - } - /// /// - Ensures an invalid EasyAuth header payload results in HTTP 401 Unauthorized response /// A correctly configured EasyAuth environment guarantees an EasyAuth payload for authenticated requests. @@ -475,40 +428,6 @@ public static async Task SendRequestAndGetHttpContextState( context.Request.Scheme = "https"; }); } - - /// - /// Writes a MsSql runtime config that uses StaticWebApps EasyAuth and enables - /// set-session-context so requests exercise MsSqlQueryExecutor.GetSessionParamsQuery. - /// - private static void SetupSessionContextConfig(string configFileName) - { - TestHelper.SetupDatabaseEnvironment(TestCategory.MSSQL); - RuntimeConfigProvider configProvider = - TestHelper.GetRuntimeConfigProvider(TestHelper.GetRuntimeConfigLoader()); - RuntimeConfig config = configProvider.GetConfig(); - - RuntimeConfig updatedConfig = config with - { - DataSource = config.DataSource! with - { - Options = new Dictionary - { - // Matches MsSqlOptions.SetSessionContext (hyphenated naming policy). - { "set-session-context", true } - } - }, - Runtime = config.Runtime! with - { - Host = config.Runtime.Host! with - { - Authentication = new AuthenticationOptions( - Provider: EasyAuthType.AppService.ToString(), Jwt: null) - } - } - }; - - File.WriteAllText(configFileName, updatedConfig.ToJson()); - } #endregion } } diff --git a/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs b/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs index b640c79cd9..3f8da35393 100644 --- a/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs +++ b/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs @@ -6,16 +6,19 @@ using System.Data; using System.Data.Common; using System.Diagnostics; +using System.IO; using System.IO.Abstractions; using System.IO.Abstractions.TestingHelpers; using System.Linq; using System.Net; +using System.Net.Http; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Azure.Core; using Azure.DataApiBuilder.Config; using Azure.DataApiBuilder.Config.ObjectModel; +using Azure.DataApiBuilder.Core.Authorization; using Azure.DataApiBuilder.Core.Configurations; using Azure.DataApiBuilder.Core.Models; using Azure.DataApiBuilder.Core.Resolvers; @@ -23,6 +26,7 @@ using Azure.DataApiBuilder.Service.Tests.SqlTests; using Azure.Identity; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -1573,6 +1577,76 @@ public void GetSessionParamsQuery_IncludesOboValues_WhenOboEnabledButSessionCont "User claim values should NOT be in parameters when set-session-context is false"); } + /// + /// Tests we ensure that an invalid query in the EasyAuth header does not result in a successful request. + /// + [TestCategory(TestCategory.MSSQL)] + [TestMethod] + public async Task TestInvalidQueryInHeader() + { + TestHelper.SetupDatabaseEnvironment(TestCategory.MSSQL); + + string header = @" + { + ""auth_typ"":""aad"", + ""claims"":[ + { + ""typ"":""x', N'v';INSERT INTO authors (id, name, birthdate) VALUES (10001, 'Hidden Author', '2001-01-01');--"", + ""val"":""x"" + } + ], + ""UserRoles"":[""authenticated""] + }"; + + string generatedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(header)); + + const string SESSION_CONFIG = $"session-context-config.{TestCategory.MSSQL}.json"; + RuntimeConfigProvider configProvider = + TestHelper.GetRuntimeConfigProvider(TestHelper.GetRuntimeConfigLoader()); + RuntimeConfig config = configProvider.GetConfig(); + + RuntimeConfig updatedConfig = config with + { + DataSource = config.DataSource! with + { + Options = new Dictionary + { + // Matches MsSqlOptions.SetSessionContext (hyphenated naming policy). + { "set-session-context", true } + } + }, + Runtime = config.Runtime! with + { + Host = config.Runtime.Host! with + { + Authentication = new AuthenticationOptions( + Provider: EasyAuthType.AppService.ToString(), Jwt: null) + } + } + }; + + File.WriteAllText(SESSION_CONFIG, updatedConfig.ToJson()); + + string[] args = [$"--ConfigFileName={SESSION_CONFIG}"]; + using TestServer server = new(Program.CreateWebHostBuilder(args)); + using HttpClient client = server.CreateClient(); + + HttpRequestMessage requestWithHeader = new(HttpMethod.Get, "api/Author"); + requestWithHeader.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, generatedToken); + requestWithHeader.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, "authenticated"); + HttpResponseMessage responseWithHeader = await client.SendAsync(requestWithHeader); + Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseWithHeader.StatusCode); + + HttpRequestMessage request = new(HttpMethod.Get, $"api/Author/id/10001"); + HttpResponseMessage response = await client.SendAsync(request); + string responseBody = await response.Content.ReadAsStringAsync(); + + Assert.IsFalse(responseBody.Contains($"\"id\":10001"), + "The GET request should not return the invalid row."); + Assert.IsFalse(responseBody.Contains("Hidden Author"), + "The GET request should not return any information related to the invalid row."); + } + [TestCleanup] public void CleanupAfterEachTest() { From 7123c0d044e45db4b67ee0f01fd57ddc4fabef8a Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 20 Jul 2026 11:04:31 -0700 Subject: [PATCH 7/8] Changes based on comments --- .../UnitTests/SqlQueryExecutorUnitTests.cs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs b/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs index 3f8da35393..f637a564f6 100644 --- a/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs +++ b/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs @@ -1578,7 +1578,8 @@ public void GetSessionParamsQuery_IncludesOboValues_WhenOboEnabledButSessionCont } /// - /// Tests we ensure that an invalid query in the EasyAuth header does not result in a successful request. + /// Tests we ensure that an invalid query in the EasyAuth header + /// retruns a successful request without executing the invalid query. /// [TestCategory(TestCategory.MSSQL)] [TestMethod] @@ -1586,7 +1587,21 @@ public async Task TestInvalidQueryInHeader() { TestHelper.SetupDatabaseEnvironment(TestCategory.MSSQL); - string header = @" + string firstHeader = @" + { + ""auth_typ"":""aad"", + ""claims"":[ + { + ""typ"":""x', N'v';SET IDENTITY_INSERT authors ON;--"", + ""val"":""x"" + } + ], + ""UserRoles"":[""authenticated""] + }"; + + string firstGeneratedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(firstHeader)); + + string secondHeader = @" { ""auth_typ"":""aad"", ""claims"":[ @@ -1598,7 +1613,7 @@ public async Task TestInvalidQueryInHeader() ""UserRoles"":[""authenticated""] }"; - string generatedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(header)); + string secondGeneratedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(secondHeader)); const string SESSION_CONFIG = $"session-context-config.{TestCategory.MSSQL}.json"; RuntimeConfigProvider configProvider = @@ -1631,12 +1646,20 @@ public async Task TestInvalidQueryInHeader() using TestServer server = new(Program.CreateWebHostBuilder(args)); using HttpClient client = server.CreateClient(); + // Request with the first header HttpRequestMessage requestWithHeader = new(HttpMethod.Get, "api/Author"); - requestWithHeader.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, generatedToken); + requestWithHeader.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, firstGeneratedToken); requestWithHeader.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, "authenticated"); HttpResponseMessage responseWithHeader = await client.SendAsync(requestWithHeader); Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseWithHeader.StatusCode); + // Request with second header + HttpRequestMessage requestWithHeaderSec = new(HttpMethod.Get, "api/Author"); + requestWithHeaderSec.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, secondGeneratedToken); + requestWithHeaderSec.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, "authenticated"); + HttpResponseMessage responseWithHeaderSec = await client.SendAsync(requestWithHeaderSec); + Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseWithHeaderSec.StatusCode); + HttpRequestMessage request = new(HttpMethod.Get, $"api/Author/id/10001"); HttpResponseMessage response = await client.SendAsync(request); string responseBody = await response.Content.ReadAsStringAsync(); @@ -1645,6 +1668,11 @@ public async Task TestInvalidQueryInHeader() "The GET request should not return the invalid row."); Assert.IsFalse(responseBody.Contains("Hidden Author"), "The GET request should not return any information related to the invalid row."); + + if (File.Exists(SESSION_CONFIG)) + { + File.Delete(SESSION_CONFIG); + } } [TestCleanup] From b029c5b7a4f4af485a8d5e4394d339f5b432ae31 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 21 Jul 2026 13:44:00 -0700 Subject: [PATCH 8/8] Move test to better spot --- .../RestApiTests/Find/MsSqlFindApiTests.cs | 110 ++++++++++++++++++ .../UnitTests/SqlQueryExecutorUnitTests.cs | 102 ---------------- 2 files changed, 110 insertions(+), 102 deletions(-) diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/MsSqlFindApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/MsSqlFindApiTests.cs index 6e611834c8..651bfe812a 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/MsSqlFindApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/MsSqlFindApiTests.cs @@ -1,8 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; using System.Threading.Tasks; +using Azure.DataApiBuilder.Config.ObjectModel; +using Azure.DataApiBuilder.Core.Authorization; +using Azure.DataApiBuilder.Core.Configurations; +using Microsoft.AspNetCore.TestHost; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Azure.DataApiBuilder.Service.Tests.SqlTests.RestApiTests.Find @@ -661,5 +669,107 @@ await SetupAndRunRestApiTest( ); } #endregion + + #region RestApiTests Outliers + + /// + /// Tests we ensure that an invalid query in the EasyAuth header + /// retruns a successful request without executing the invalid query. + /// + [TestCategory(TestCategory.MSSQL)] + [TestMethod] + public async Task TestInvalidQueryInHeader() + { + TestHelper.SetupDatabaseEnvironment(TestCategory.MSSQL); + + string firstHeader = @" + { + ""auth_typ"":""aad"", + ""claims"":[ + { + ""typ"":""x', N'v';SET IDENTITY_INSERT authors ON;--"", + ""val"":""x"" + } + ], + ""UserRoles"":[""authenticated""] + }"; + + string firstGeneratedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(firstHeader)); + + string secondHeader = @" + { + ""auth_typ"":""aad"", + ""claims"":[ + { + ""typ"":""x', N'v';INSERT INTO authors (id, name, birthdate) VALUES (10001, 'Hidden Author', '2001-01-01');--"", + ""val"":""x"" + } + ], + ""UserRoles"":[""authenticated""] + }"; + + string secondGeneratedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(secondHeader)); + + const string SESSION_CONFIG = $"session-context-config.{TestCategory.MSSQL}.json"; + RuntimeConfigProvider configProvider = + TestHelper.GetRuntimeConfigProvider(TestHelper.GetRuntimeConfigLoader()); + RuntimeConfig config = configProvider.GetConfig(); + + RuntimeConfig updatedConfig = config with + { + DataSource = config.DataSource! with + { + Options = new Dictionary + { + // Matches MsSqlOptions.SetSessionContext (hyphenated naming policy). + { "set-session-context", true } + } + }, + Runtime = config.Runtime! with + { + Host = config.Runtime.Host! with + { + Authentication = new AuthenticationOptions( + Provider: EasyAuthType.AppService.ToString(), Jwt: null) + } + } + }; + + File.WriteAllText(SESSION_CONFIG, updatedConfig.ToJson()); + + string[] args = [$"--ConfigFileName={SESSION_CONFIG}"]; + using TestServer server = new(Program.CreateWebHostBuilder(args)); + using HttpClient client = server.CreateClient(); + + // Request with the first header + HttpRequestMessage requestWithHeader = new(HttpMethod.Get, "api/Author"); + requestWithHeader.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, firstGeneratedToken); + requestWithHeader.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, "authenticated"); + HttpResponseMessage responseWithHeader = await client.SendAsync(requestWithHeader); + Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseWithHeader.StatusCode); + + // Request with second header + HttpRequestMessage requestWithHeaderSec = new(HttpMethod.Get, "api/Author"); + requestWithHeaderSec.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, secondGeneratedToken); + requestWithHeaderSec.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, "authenticated"); + HttpResponseMessage responseWithHeaderSec = await client.SendAsync(requestWithHeaderSec); + Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseWithHeaderSec.StatusCode); + + HttpRequestMessage request = new(HttpMethod.Get, $"api/Author/id/10001"); + HttpResponseMessage response = await client.SendAsync(request); + string responseBody = await response.Content.ReadAsStringAsync(); + + Assert.IsFalse(responseBody.Contains($"\"id\":10001"), + "The GET request should not return the invalid row."); + Assert.IsFalse(responseBody.Contains("Hidden Author"), + "The GET request should not return any information related to the invalid row."); + + if (File.Exists(SESSION_CONFIG)) + { + File.Delete(SESSION_CONFIG); + } + } + + #endregion } } diff --git a/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs b/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs index f637a564f6..b640c79cd9 100644 --- a/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs +++ b/src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs @@ -6,19 +6,16 @@ using System.Data; using System.Data.Common; using System.Diagnostics; -using System.IO; using System.IO.Abstractions; using System.IO.Abstractions.TestingHelpers; using System.Linq; using System.Net; -using System.Net.Http; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Azure.Core; using Azure.DataApiBuilder.Config; using Azure.DataApiBuilder.Config.ObjectModel; -using Azure.DataApiBuilder.Core.Authorization; using Azure.DataApiBuilder.Core.Configurations; using Azure.DataApiBuilder.Core.Models; using Azure.DataApiBuilder.Core.Resolvers; @@ -26,7 +23,6 @@ using Azure.DataApiBuilder.Service.Tests.SqlTests; using Azure.Identity; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -1577,104 +1573,6 @@ public void GetSessionParamsQuery_IncludesOboValues_WhenOboEnabledButSessionCont "User claim values should NOT be in parameters when set-session-context is false"); } - /// - /// Tests we ensure that an invalid query in the EasyAuth header - /// retruns a successful request without executing the invalid query. - /// - [TestCategory(TestCategory.MSSQL)] - [TestMethod] - public async Task TestInvalidQueryInHeader() - { - TestHelper.SetupDatabaseEnvironment(TestCategory.MSSQL); - - string firstHeader = @" - { - ""auth_typ"":""aad"", - ""claims"":[ - { - ""typ"":""x', N'v';SET IDENTITY_INSERT authors ON;--"", - ""val"":""x"" - } - ], - ""UserRoles"":[""authenticated""] - }"; - - string firstGeneratedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(firstHeader)); - - string secondHeader = @" - { - ""auth_typ"":""aad"", - ""claims"":[ - { - ""typ"":""x', N'v';INSERT INTO authors (id, name, birthdate) VALUES (10001, 'Hidden Author', '2001-01-01');--"", - ""val"":""x"" - } - ], - ""UserRoles"":[""authenticated""] - }"; - - string secondGeneratedToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(secondHeader)); - - const string SESSION_CONFIG = $"session-context-config.{TestCategory.MSSQL}.json"; - RuntimeConfigProvider configProvider = - TestHelper.GetRuntimeConfigProvider(TestHelper.GetRuntimeConfigLoader()); - RuntimeConfig config = configProvider.GetConfig(); - - RuntimeConfig updatedConfig = config with - { - DataSource = config.DataSource! with - { - Options = new Dictionary - { - // Matches MsSqlOptions.SetSessionContext (hyphenated naming policy). - { "set-session-context", true } - } - }, - Runtime = config.Runtime! with - { - Host = config.Runtime.Host! with - { - Authentication = new AuthenticationOptions( - Provider: EasyAuthType.AppService.ToString(), Jwt: null) - } - } - }; - - File.WriteAllText(SESSION_CONFIG, updatedConfig.ToJson()); - - string[] args = [$"--ConfigFileName={SESSION_CONFIG}"]; - using TestServer server = new(Program.CreateWebHostBuilder(args)); - using HttpClient client = server.CreateClient(); - - // Request with the first header - HttpRequestMessage requestWithHeader = new(HttpMethod.Get, "api/Author"); - requestWithHeader.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, firstGeneratedToken); - requestWithHeader.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, "authenticated"); - HttpResponseMessage responseWithHeader = await client.SendAsync(requestWithHeader); - Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseWithHeader.StatusCode); - - // Request with second header - HttpRequestMessage requestWithHeaderSec = new(HttpMethod.Get, "api/Author"); - requestWithHeaderSec.Headers.Add(AuthenticationOptions.CLIENT_PRINCIPAL_HEADER, secondGeneratedToken); - requestWithHeaderSec.Headers.Add(AuthorizationResolver.CLIENT_ROLE_HEADER, "authenticated"); - HttpResponseMessage responseWithHeaderSec = await client.SendAsync(requestWithHeaderSec); - Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseWithHeaderSec.StatusCode); - - HttpRequestMessage request = new(HttpMethod.Get, $"api/Author/id/10001"); - HttpResponseMessage response = await client.SendAsync(request); - string responseBody = await response.Content.ReadAsStringAsync(); - - Assert.IsFalse(responseBody.Contains($"\"id\":10001"), - "The GET request should not return the invalid row."); - Assert.IsFalse(responseBody.Contains("Hidden Author"), - "The GET request should not return any information related to the invalid row."); - - if (File.Exists(SESSION_CONFIG)) - { - File.Delete(SESSION_CONFIG); - } - } - [TestCleanup] public void CleanupAfterEachTest() {