diff --git a/src/Core/Resolvers/MsSqlQueryExecutor.cs b/src/Core/Resolvers/MsSqlQueryExecutor.cs
index b4f84757b3..2078b4f1c5 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,14 @@ public override string GetSessionParamsQuery(HttpContext? httpContext, IDictiona
foreach ((string claimType, string claimValue) in sessionParams)
{
+ string keyName = $"{SESSION_KEY_NAME}{counter.Current()}";
+ 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 " + $"'{claimType}', " + paramName + ", @read_only = 0;";
+ 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 35ef925dab..4caf9a3a7a 100644
--- a/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs
+++ b/src/Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs
@@ -325,7 +325,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);
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
}
}