Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Core/Resolvers/MsSqlQueryExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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.
Comment thread
RubenCerna2079 marked this conversation as resolved.
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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
// Licensed under the MIT License.

#nullable enable
using System;
using System.IO;
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;
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;
Expand Down Expand Up @@ -325,7 +329,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);
Expand All @@ -344,6 +347,49 @@ await SendRequestAndGetHttpContextState(
ignoreCase: true);
}

/// <summary>
/// Tests we ensure that an invalid query in the EasyAuth header does not result in a successful request.
/// </summary>
[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();
Comment thread
RubenCerna2079 marked this conversation as resolved.

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.");
}

/// <summary>
/// - Ensures an invalid EasyAuth header payload results in HTTP 401 Unauthorized response
/// A correctly configured EasyAuth environment guarantees an EasyAuth payload for authenticated requests.
Expand Down Expand Up @@ -429,6 +475,40 @@ public static async Task<HttpContext> SendRequestAndGetHttpContextState(
context.Request.Scheme = "https";
});
}

/// <summary>
/// Writes a MsSql runtime config that uses StaticWebApps EasyAuth and enables
/// set-session-context so requests exercise MsSqlQueryExecutor.GetSessionParamsQuery.
/// </summary>
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<string, object?>
{
// 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
}
}
Loading