From f246618bdb6a19f2f309a933df4f6f36c4b6a34c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 22:30:09 +0000
Subject: [PATCH 01/13] Initial plan
From 4792325b0cfde036c66b90a3fb2e38d089b2aab2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 22:37:56 +0000
Subject: [PATCH 02/13] Add CLI support for user-delegated-auth configuration
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
src/Cli.Tests/ConfigureOptionsTests.cs | 181 ++++++++++++++++++
src/Cli/Commands/ConfigureOptions.cs | 10 +
src/Cli/ConfigGenerator.cs | 28 ++-
.../Converters/DataSourceConverterFactory.cs | 17 +-
src/Config/ObjectModel/DataSource.cs | 4 +-
.../ObjectModel/UserDelegatedAuthConfig.cs | 59 ++++++
6 files changed, 296 insertions(+), 3 deletions(-)
create mode 100644 src/Config/ObjectModel/UserDelegatedAuthConfig.cs
diff --git a/src/Cli.Tests/ConfigureOptionsTests.cs b/src/Cli.Tests/ConfigureOptionsTests.cs
index b368227a75..8ab024ae0b 100644
--- a/src/Cli.Tests/ConfigureOptionsTests.cs
+++ b/src/Cli.Tests/ConfigureOptionsTests.cs
@@ -1094,5 +1094,186 @@ private void SetupFileSystemWithInitialConfig(string jsonConfig)
Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(jsonConfig, out RuntimeConfig? config));
Assert.IsNotNull(config.Runtime);
}
+
+ ///
+ /// Tests adding user-delegated-auth.enabled to a config that doesn't have user-delegated-auth configured.
+ /// This method verifies that the user-delegated-auth.enabled property can be set to true for MSSQL database.
+ /// Command: dab configure --data-source.user-delegated-auth.enabled true
+ ///
+ [TestMethod]
+ public void TestAddUserDelegatedAuthEnabled()
+ {
+ // Arrange
+ SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
+
+ ConfigureOptions options = new(
+ dataSourceUserDelegatedAuthEnabled: true,
+ config: TEST_RUNTIME_CONFIG_FILE
+ );
+
+ // Act
+ bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
+
+ // Assert
+ Assert.IsTrue(isSuccess);
+ string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
+ Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config));
+ Assert.IsNotNull(config.DataSource);
+ Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
+ Assert.IsTrue(config.DataSource.UserDelegatedAuth.Enabled);
+ }
+
+ ///
+ /// Tests adding user-delegated-auth.database-audience to a config that doesn't have user-delegated-auth configured.
+ /// This method verifies that the database-audience can be set for user-delegated authentication.
+ /// Command: dab configure --data-source.user-delegated-auth.database-audience "https://database.windows.net"
+ ///
+ [TestMethod]
+ public void TestAddUserDelegatedAuthDatabaseAudience()
+ {
+ // Arrange
+ SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
+ string audienceValue = "https://database.windows.net";
+
+ ConfigureOptions options = new(
+ dataSourceUserDelegatedAuthDatabaseAudience: audienceValue,
+ config: TEST_RUNTIME_CONFIG_FILE
+ );
+
+ // Act
+ bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
+
+ // Assert
+ Assert.IsTrue(isSuccess);
+ string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
+ Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config));
+ Assert.IsNotNull(config.DataSource);
+ Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
+ Assert.AreEqual(audienceValue, config.DataSource.UserDelegatedAuth.DatabaseAudience);
+ }
+
+ ///
+ /// Tests adding both user-delegated-auth.enabled and database-audience in a single command.
+ /// This method verifies that both properties can be set together.
+ /// Command: dab configure --data-source.user-delegated-auth.enabled true --data-source.user-delegated-auth.database-audience "https://database.windows.net"
+ ///
+ [DataTestMethod]
+ [DataRow("https://database.windows.net", DisplayName = "Azure SQL Database (public cloud)")]
+ [DataRow("https://database.usgovcloudapi.net", DisplayName = "Azure Government Cloud")]
+ [DataRow("https://database.chinacloudapi.cn", DisplayName = "Azure China Cloud")]
+ [DataRow("https://myinstance.abc123.database.windows.net", DisplayName = "Azure SQL Managed Instance")]
+ public void TestAddUserDelegatedAuthEnabledAndDatabaseAudience(string audienceValue)
+ {
+ // Arrange
+ SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
+
+ ConfigureOptions options = new(
+ dataSourceUserDelegatedAuthEnabled: true,
+ dataSourceUserDelegatedAuthDatabaseAudience: audienceValue,
+ config: TEST_RUNTIME_CONFIG_FILE
+ );
+
+ // Act
+ bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
+
+ // Assert
+ Assert.IsTrue(isSuccess);
+ string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
+ Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config));
+ Assert.IsNotNull(config.DataSource);
+ Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
+ Assert.IsTrue(config.DataSource.UserDelegatedAuth.Enabled);
+ Assert.AreEqual(audienceValue, config.DataSource.UserDelegatedAuth.DatabaseAudience);
+ }
+
+ ///
+ /// Tests that enabling user-delegated-auth on a non-MSSQL database fails.
+ /// This method verifies that user-delegated-auth is only allowed for MSSQL database type.
+ /// Command: dab configure --data-source.database-type postgresql --data-source.user-delegated-auth.enabled true
+ ///
+ [DataTestMethod]
+ [DataRow("postgresql", DisplayName = "Fail when enabling user-delegated-auth on PostgreSQL")]
+ [DataRow("mysql", DisplayName = "Fail when enabling user-delegated-auth on MySQL")]
+ [DataRow("cosmosdb_nosql", DisplayName = "Fail when enabling user-delegated-auth on CosmosDB")]
+ public void TestFailureWhenEnablingUserDelegatedAuthOnNonMSSQLDatabase(string dbType)
+ {
+ // Arrange
+ SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
+
+ ConfigureOptions options = new(
+ dataSourceDatabaseType: dbType,
+ dataSourceUserDelegatedAuthEnabled: true,
+ config: TEST_RUNTIME_CONFIG_FILE
+ );
+
+ // Act
+ bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
+
+ // Assert
+ Assert.IsFalse(isSuccess);
+ }
+
+ ///
+ /// Tests updating existing user-delegated-auth configuration by changing the database-audience.
+ /// This method verifies that the database-audience can be updated while preserving the enabled setting.
+ ///
+ [TestMethod]
+ public void TestUpdateUserDelegatedAuthDatabaseAudience()
+ {
+ // Arrange - Config with existing user-delegated-auth section
+ string configWithUserDelegatedAuth = @"
+ {
+ ""$schema"": ""test"",
+ ""data-source"": {
+ ""database-type"": ""mssql"",
+ ""connection-string"": ""testconnectionstring"",
+ ""user-delegated-auth"": {
+ ""enabled"": true,
+ ""database-audience"": ""https://database.windows.net""
+ }
+ },
+ ""runtime"": {
+ ""rest"": {
+ ""enabled"": true,
+ ""path"": ""/api""
+ },
+ ""graphql"": {
+ ""enabled"": true,
+ ""path"": ""/graphql"",
+ ""allow-introspection"": true
+ },
+ ""host"": {
+ ""mode"": ""development"",
+ ""cors"": {
+ ""origins"": [],
+ ""allow-credentials"": false
+ },
+ ""authentication"": {
+ ""provider"": ""StaticWebApps""
+ }
+ }
+ },
+ ""entities"": {}
+ }";
+ SetupFileSystemWithInitialConfig(configWithUserDelegatedAuth);
+
+ string newAudience = "https://database.usgovcloudapi.net";
+ ConfigureOptions options = new(
+ dataSourceUserDelegatedAuthDatabaseAudience: newAudience,
+ config: TEST_RUNTIME_CONFIG_FILE
+ );
+
+ // Act
+ bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
+
+ // Assert
+ Assert.IsTrue(isSuccess);
+ string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
+ Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config));
+ Assert.IsNotNull(config.DataSource);
+ Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
+ Assert.IsTrue(config.DataSource.UserDelegatedAuth.Enabled);
+ Assert.AreEqual(newAudience, config.DataSource.UserDelegatedAuth.DatabaseAudience);
+ }
}
}
diff --git a/src/Cli/Commands/ConfigureOptions.cs b/src/Cli/Commands/ConfigureOptions.cs
index 14234d24d7..069a2d3b6a 100644
--- a/src/Cli/Commands/ConfigureOptions.cs
+++ b/src/Cli/Commands/ConfigureOptions.cs
@@ -29,6 +29,8 @@ public ConfigureOptions(
string? dataSourceOptionsSchema = null,
bool? dataSourceOptionsSetSessionContext = null,
string? dataSourceHealthName = null,
+ bool? dataSourceUserDelegatedAuthEnabled = null,
+ string? dataSourceUserDelegatedAuthDatabaseAudience = null,
int? depthLimit = null,
bool? runtimeGraphQLEnabled = null,
string? runtimeGraphQLPath = null,
@@ -84,6 +86,8 @@ public ConfigureOptions(
DataSourceOptionsSchema = dataSourceOptionsSchema;
DataSourceOptionsSetSessionContext = dataSourceOptionsSetSessionContext;
DataSourceHealthName = dataSourceHealthName;
+ DataSourceUserDelegatedAuthEnabled = dataSourceUserDelegatedAuthEnabled;
+ DataSourceUserDelegatedAuthDatabaseAudience = dataSourceUserDelegatedAuthDatabaseAudience;
// GraphQL
DepthLimit = depthLimit;
RuntimeGraphQLEnabled = runtimeGraphQLEnabled;
@@ -160,6 +164,12 @@ public ConfigureOptions(
[Option("data-source.health.name", Required = false, HelpText = "Identifier for data source in health check report.")]
public string? DataSourceHealthName { get; }
+ [Option("data-source.user-delegated-auth.enabled", Required = false, HelpText = "Enable user-delegated authentication (OBO) for Azure SQL. Default: false (boolean).")]
+ public bool? DataSourceUserDelegatedAuthEnabled { get; }
+
+ [Option("data-source.user-delegated-auth.database-audience", Required = false, HelpText = "Azure SQL resource identifier for token acquisition (e.g., https://database.windows.net).")]
+ public string? DataSourceUserDelegatedAuthDatabaseAudience { get; }
+
[Option("runtime.graphql.depth-limit", Required = false, HelpText = "Max allowed depth of the nested query. Allowed values: (0,2147483647] inclusive. Default is infinity. Use -1 to remove limit.")]
public int? DepthLimit { get; }
diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs
index 9a3401a55a..0f77004835 100644
--- a/src/Cli/ConfigGenerator.cs
+++ b/src/Cli/ConfigGenerator.cs
@@ -643,6 +643,7 @@ private static bool TryUpdateConfiguredDataSourceOptions(
DatabaseType dbType = runtimeConfig.DataSource.DatabaseType;
string dataSourceConnectionString = runtimeConfig.DataSource.ConnectionString;
DatasourceHealthCheckConfig? datasourceHealthCheckConfig = runtimeConfig.DataSource.Health;
+ UserDelegatedAuthConfig? userDelegatedAuthConfig = runtimeConfig.DataSource.UserDelegatedAuth;
if (options.DataSourceDatabaseType is not null)
{
@@ -714,8 +715,33 @@ private static bool TryUpdateConfiguredDataSourceOptions(
}
}
+ // Handle user-delegated-auth options
+ if (options.DataSourceUserDelegatedAuthEnabled is not null
+ || options.DataSourceUserDelegatedAuthDatabaseAudience is not null)
+ {
+ // Validate that user-delegated-auth is only used with MSSQL
+ if (options.DataSourceUserDelegatedAuthEnabled == true && !DatabaseType.MSSQL.Equals(dbType))
+ {
+ _logger.LogError("user-delegated-auth is only supported for database-type 'mssql'.");
+ return false;
+ }
+
+ // Create or update user-delegated-auth config
+ bool enabled = options.DataSourceUserDelegatedAuthEnabled
+ ?? userDelegatedAuthConfig?.Enabled
+ ?? false;
+ string? databaseAudience = options.DataSourceUserDelegatedAuthDatabaseAudience
+ ?? userDelegatedAuthConfig?.DatabaseAudience;
+
+ userDelegatedAuthConfig = new UserDelegatedAuthConfig(
+ Enabled: enabled,
+ DatabaseAudience: databaseAudience,
+ DisableConnectionPooling: userDelegatedAuthConfig?.DisableConnectionPooling,
+ TokenCacheDurationMinutes: userDelegatedAuthConfig?.TokenCacheDurationMinutes);
+ }
+
dbOptions = EnumerableUtilities.IsNullOrEmpty(dbOptions) ? null : dbOptions;
- DataSource dataSource = new(dbType, dataSourceConnectionString, dbOptions, datasourceHealthCheckConfig);
+ DataSource dataSource = new(dbType, dataSourceConnectionString, dbOptions, datasourceHealthCheckConfig, userDelegatedAuthConfig);
runtimeConfig = runtimeConfig with { DataSource = dataSource };
return runtimeConfig != null;
diff --git a/src/Config/Converters/DataSourceConverterFactory.cs b/src/Config/Converters/DataSourceConverterFactory.cs
index 1788ebf2b4..3f697061b2 100644
--- a/src/Config/Converters/DataSourceConverterFactory.cs
+++ b/src/Config/Converters/DataSourceConverterFactory.cs
@@ -51,12 +51,13 @@ public DataSourceConverter(DeserializationVariableReplacementSettings? replaceme
string connectionString = string.Empty;
DatasourceHealthCheckConfig? health = null;
Dictionary? datasourceOptions = null;
+ UserDelegatedAuthConfig? userDelegatedAuth = null;
while (reader.Read())
{
if (reader.TokenType is JsonTokenType.EndObject)
{
- return new DataSource(databaseType, connectionString, datasourceOptions, health);
+ return new DataSource(databaseType, connectionString, datasourceOptions, health, userDelegatedAuth);
}
if (reader.TokenType is JsonTokenType.PropertyName)
@@ -91,6 +92,20 @@ public DataSourceConverter(DeserializationVariableReplacementSettings? replaceme
}
}
+ break;
+ case "user-delegated-auth":
+ if (reader.TokenType is not JsonTokenType.Null)
+ {
+ try
+ {
+ userDelegatedAuth = JsonSerializer.Deserialize(ref reader, options);
+ }
+ catch (Exception e)
+ {
+ throw new JsonException($"Error while deserializing DataSource user-delegated-auth: {e.Message}");
+ }
+ }
+
break;
case "options":
if (reader.TokenType is not JsonTokenType.Null)
diff --git a/src/Config/ObjectModel/DataSource.cs b/src/Config/ObjectModel/DataSource.cs
index d1a2456ef9..769e109a43 100644
--- a/src/Config/ObjectModel/DataSource.cs
+++ b/src/Config/ObjectModel/DataSource.cs
@@ -14,11 +14,13 @@ namespace Azure.DataApiBuilder.Config.ObjectModel;
/// Connection string to access the database.
/// Custom options for the specific database. If there are no options, this could be null.
/// Health check configuration for the datasource.
+/// User-delegated authentication configuration (OBO). Optional.
public record DataSource(
DatabaseType DatabaseType,
string ConnectionString,
Dictionary? Options = null,
- DatasourceHealthCheckConfig? Health = null)
+ DatasourceHealthCheckConfig? Health = null,
+ UserDelegatedAuthConfig? UserDelegatedAuth = null)
{
[JsonIgnore]
public bool IsDatasourceHealthEnabled =>
diff --git a/src/Config/ObjectModel/UserDelegatedAuthConfig.cs b/src/Config/ObjectModel/UserDelegatedAuthConfig.cs
new file mode 100644
index 0000000000..3fd55e3d2f
--- /dev/null
+++ b/src/Config/ObjectModel/UserDelegatedAuthConfig.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.DataApiBuilder.Config.ObjectModel;
+
+///
+/// Configuration for user-delegated authentication (OBO - On-Behalf-Of).
+/// Enables per-user Entra ID access token authentication to Azure SQL.
+///
+/// Whether user-delegated authentication is enabled.
+/// The Azure SQL resource identifier for token acquisition.
+/// Explicitly control connection pooling behavior. Default: true for safety.
+/// In-memory cache duration for OBO tokens per user. Default: 50 minutes.
+public record UserDelegatedAuthConfig(
+ bool Enabled = false,
+ string? DatabaseAudience = null,
+ bool? DisableConnectionPooling = null,
+ int? TokenCacheDurationMinutes = null)
+{
+ ///
+ /// Default value for token cache duration in minutes.
+ /// Must be less than typical token lifetime (60 min).
+ ///
+ public const int DEFAULT_TOKEN_CACHE_DURATION_MINUTES = 50;
+
+ ///
+ /// Default value for connection pooling (disabled for safety in MVP).
+ ///
+ public const bool DEFAULT_DISABLE_CONNECTION_POOLING = true;
+
+ ///
+ /// Minimum allowed token cache duration in minutes.
+ ///
+ public const int MIN_TOKEN_CACHE_DURATION_MINUTES = 1;
+
+ ///
+ /// Maximum allowed token cache duration in minutes.
+ /// Must be less than typical token lifetime (60 min).
+ ///
+ public const int MAX_TOKEN_CACHE_DURATION_MINUTES = 59;
+
+ ///
+ /// Gets the effective token cache duration value.
+ /// Returns the configured value or the default if not specified.
+ ///
+ [JsonIgnore]
+ public int EffectiveTokenCacheDurationMinutes =>
+ TokenCacheDurationMinutes ?? DEFAULT_TOKEN_CACHE_DURATION_MINUTES;
+
+ ///
+ /// Gets the effective connection pooling setting.
+ /// Returns the configured value or the default if not specified.
+ ///
+ [JsonIgnore]
+ public bool EffectiveDisableConnectionPooling =>
+ DisableConnectionPooling ?? DEFAULT_DISABLE_CONNECTION_POOLING;
+}
From 122f503e512170b4af0a48ce232aa1aa6dac0b52 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 22:42:51 +0000
Subject: [PATCH 03/13] Add runtime parsing tests and verify CLI functionality
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
.../UserDelegatedAuthRuntimeParsingTests.cs | 103 ++++++++++++++++++
1 file changed, 103 insertions(+)
create mode 100644 src/Cli.Tests/UserDelegatedAuthRuntimeParsingTests.cs
diff --git a/src/Cli.Tests/UserDelegatedAuthRuntimeParsingTests.cs b/src/Cli.Tests/UserDelegatedAuthRuntimeParsingTests.cs
new file mode 100644
index 0000000000..7a463a845c
--- /dev/null
+++ b/src/Cli.Tests/UserDelegatedAuthRuntimeParsingTests.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Cli.Tests
+{
+ [TestClass]
+ public class UserDelegatedAuthRuntimeParsingTests
+ {
+ [TestMethod]
+ public void TestRuntimeCanParseUserDelegatedAuthConfig()
+ {
+ // Arrange
+ string configJson = @"{
+ ""$schema"": ""test"",
+ ""data-source"": {
+ ""database-type"": ""mssql"",
+ ""connection-string"": ""testconnectionstring"",
+ ""user-delegated-auth"": {
+ ""enabled"": true,
+ ""database-audience"": ""https://database.windows.net""
+ }
+ },
+ ""runtime"": {
+ ""rest"": {
+ ""enabled"": true,
+ ""path"": ""/api""
+ },
+ ""graphql"": {
+ ""enabled"": true,
+ ""path"": ""/graphql"",
+ ""allow-introspection"": true
+ },
+ ""host"": {
+ ""mode"": ""development"",
+ ""cors"": {
+ ""origins"": [],
+ ""allow-credentials"": false
+ },
+ ""authentication"": {
+ ""provider"": ""StaticWebApps""
+ }
+ }
+ },
+ ""entities"": {}
+ }";
+
+ // Act
+ bool success = RuntimeConfigLoader.TryParseConfig(configJson, out RuntimeConfig? config);
+
+ // Assert
+ Assert.IsTrue(success);
+ Assert.IsNotNull(config);
+ Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
+ Assert.IsTrue(config.DataSource.UserDelegatedAuth.Enabled);
+ Assert.AreEqual("https://database.windows.net", config.DataSource.UserDelegatedAuth.DatabaseAudience);
+ Assert.AreEqual(50, config.DataSource.UserDelegatedAuth.EffectiveTokenCacheDurationMinutes);
+ Assert.IsTrue(config.DataSource.UserDelegatedAuth.EffectiveDisableConnectionPooling);
+ }
+
+ [TestMethod]
+ public void TestRuntimeCanParseConfigWithoutUserDelegatedAuth()
+ {
+ // Arrange
+ string configJson = @"{
+ ""$schema"": ""test"",
+ ""data-source"": {
+ ""database-type"": ""mssql"",
+ ""connection-string"": ""testconnectionstring""
+ },
+ ""runtime"": {
+ ""rest"": {
+ ""enabled"": true,
+ ""path"": ""/api""
+ },
+ ""graphql"": {
+ ""enabled"": true,
+ ""path"": ""/graphql"",
+ ""allow-introspection"": true
+ },
+ ""host"": {
+ ""mode"": ""development"",
+ ""cors"": {
+ ""origins"": [],
+ ""allow-credentials"": false
+ },
+ ""authentication"": {
+ ""provider"": ""StaticWebApps""
+ }
+ }
+ },
+ ""entities"": {}
+ }";
+
+ // Act
+ bool success = RuntimeConfigLoader.TryParseConfig(configJson, out RuntimeConfig? config);
+
+ // Assert
+ Assert.IsTrue(success);
+ Assert.IsNotNull(config);
+ Assert.IsNull(config.DataSource.UserDelegatedAuth);
+ }
+ }
+}
From be514c865898c441002a56cffa63105bac1f0e06 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 12 Feb 2026 22:53:11 +0000
Subject: [PATCH 04/13] Address code review feedback - improve documentation
and validation
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
src/Cli/ConfigGenerator.cs | 15 +++++++++------
src/Config/ObjectModel/UserDelegatedAuthConfig.cs | 2 +-
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs
index 0f77004835..7e5b6c400e 100644
--- a/src/Cli/ConfigGenerator.cs
+++ b/src/Cli/ConfigGenerator.cs
@@ -719,20 +719,23 @@ private static bool TryUpdateConfiguredDataSourceOptions(
if (options.DataSourceUserDelegatedAuthEnabled is not null
|| options.DataSourceUserDelegatedAuthDatabaseAudience is not null)
{
- // Validate that user-delegated-auth is only used with MSSQL
- if (options.DataSourceUserDelegatedAuthEnabled == true && !DatabaseType.MSSQL.Equals(dbType))
+ // Determine the enabled state: use new value if provided, otherwise preserve existing
+ bool enabled = options.DataSourceUserDelegatedAuthEnabled
+ ?? userDelegatedAuthConfig?.Enabled
+ ?? false;
+
+ // Validate that user-delegated-auth is only used with MSSQL when enabled=true
+ if (enabled && !DatabaseType.MSSQL.Equals(dbType))
{
_logger.LogError("user-delegated-auth is only supported for database-type 'mssql'.");
return false;
}
- // Create or update user-delegated-auth config
- bool enabled = options.DataSourceUserDelegatedAuthEnabled
- ?? userDelegatedAuthConfig?.Enabled
- ?? false;
+ // Get database-audience: use new value if provided, otherwise preserve existing
string? databaseAudience = options.DataSourceUserDelegatedAuthDatabaseAudience
?? userDelegatedAuthConfig?.DatabaseAudience;
+ // Create or update user-delegated-auth config
userDelegatedAuthConfig = new UserDelegatedAuthConfig(
Enabled: enabled,
DatabaseAudience: databaseAudience,
diff --git a/src/Config/ObjectModel/UserDelegatedAuthConfig.cs b/src/Config/ObjectModel/UserDelegatedAuthConfig.cs
index 3fd55e3d2f..ee086a4053 100644
--- a/src/Config/ObjectModel/UserDelegatedAuthConfig.cs
+++ b/src/Config/ObjectModel/UserDelegatedAuthConfig.cs
@@ -11,7 +11,7 @@ namespace Azure.DataApiBuilder.Config.ObjectModel;
///
/// Whether user-delegated authentication is enabled.
/// The Azure SQL resource identifier for token acquisition.
-/// Explicitly control connection pooling behavior. Default: true for safety.
+/// Explicitly control connection pooling behavior. Default: true (disabled) for safety. Connection pooling is disabled by default in OBO scenarios to prevent token reuse across different user contexts.
/// In-memory cache duration for OBO tokens per user. Default: 50 minutes.
public record UserDelegatedAuthConfig(
bool Enabled = false,
From b0f98fe913965652c2ab32768717bf6f6d3fa082 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 20:48:55 +0000
Subject: [PATCH 05/13] Align CLI implementation with PR #3151 - rename to
UserDelegatedAuthOptions and add provider field
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
global.json | 2 +-
.../UserDelegatedAuthRuntimeParsingTests.cs | 2 -
src/Cli/ConfigGenerator.cs | 17 ++++--
.../Converters/DataSourceConverterFactory.cs | 9 ++-
src/Config/ObjectModel/DataSource.cs | 11 +++-
.../ObjectModel/UserDelegatedAuthConfig.cs | 59 -------------------
.../ObjectModel/UserDelegatedAuthOptions.cs | 45 ++++++++++++++
7 files changed, 71 insertions(+), 74 deletions(-)
delete mode 100644 src/Config/ObjectModel/UserDelegatedAuthConfig.cs
create mode 100644 src/Config/ObjectModel/UserDelegatedAuthOptions.cs
diff --git a/global.json b/global.json
index 17811390a4..1bdb496ef0 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.418",
+ "version": "8.0.417",
"rollForward": "latestFeature"
}
}
diff --git a/src/Cli.Tests/UserDelegatedAuthRuntimeParsingTests.cs b/src/Cli.Tests/UserDelegatedAuthRuntimeParsingTests.cs
index 7a463a845c..29110a5a7c 100644
--- a/src/Cli.Tests/UserDelegatedAuthRuntimeParsingTests.cs
+++ b/src/Cli.Tests/UserDelegatedAuthRuntimeParsingTests.cs
@@ -53,8 +53,6 @@ public void TestRuntimeCanParseUserDelegatedAuthConfig()
Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
Assert.IsTrue(config.DataSource.UserDelegatedAuth.Enabled);
Assert.AreEqual("https://database.windows.net", config.DataSource.UserDelegatedAuth.DatabaseAudience);
- Assert.AreEqual(50, config.DataSource.UserDelegatedAuth.EffectiveTokenCacheDurationMinutes);
- Assert.IsTrue(config.DataSource.UserDelegatedAuth.EffectiveDisableConnectionPooling);
}
[TestMethod]
diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs
index 7e5b6c400e..6c51f002b7 100644
--- a/src/Cli/ConfigGenerator.cs
+++ b/src/Cli/ConfigGenerator.cs
@@ -643,7 +643,7 @@ private static bool TryUpdateConfiguredDataSourceOptions(
DatabaseType dbType = runtimeConfig.DataSource.DatabaseType;
string dataSourceConnectionString = runtimeConfig.DataSource.ConnectionString;
DatasourceHealthCheckConfig? datasourceHealthCheckConfig = runtimeConfig.DataSource.Health;
- UserDelegatedAuthConfig? userDelegatedAuthConfig = runtimeConfig.DataSource.UserDelegatedAuth;
+ UserDelegatedAuthOptions? userDelegatedAuthConfig = runtimeConfig.DataSource.UserDelegatedAuth;
if (options.DataSourceDatabaseType is not null)
{
@@ -735,16 +735,21 @@ private static bool TryUpdateConfiguredDataSourceOptions(
string? databaseAudience = options.DataSourceUserDelegatedAuthDatabaseAudience
?? userDelegatedAuthConfig?.DatabaseAudience;
+ // Get provider: preserve existing or use default "EntraId"
+ string? provider = userDelegatedAuthConfig?.Provider ?? "EntraId";
+
// Create or update user-delegated-auth config
- userDelegatedAuthConfig = new UserDelegatedAuthConfig(
+ userDelegatedAuthConfig = new UserDelegatedAuthOptions(
Enabled: enabled,
- DatabaseAudience: databaseAudience,
- DisableConnectionPooling: userDelegatedAuthConfig?.DisableConnectionPooling,
- TokenCacheDurationMinutes: userDelegatedAuthConfig?.TokenCacheDurationMinutes);
+ Provider: provider,
+ DatabaseAudience: databaseAudience);
}
dbOptions = EnumerableUtilities.IsNullOrEmpty(dbOptions) ? null : dbOptions;
- DataSource dataSource = new(dbType, dataSourceConnectionString, dbOptions, datasourceHealthCheckConfig, userDelegatedAuthConfig);
+ DataSource dataSource = new(dbType, dataSourceConnectionString, dbOptions, datasourceHealthCheckConfig)
+ {
+ UserDelegatedAuth = userDelegatedAuthConfig
+ };
runtimeConfig = runtimeConfig with { DataSource = dataSource };
return runtimeConfig != null;
diff --git a/src/Config/Converters/DataSourceConverterFactory.cs b/src/Config/Converters/DataSourceConverterFactory.cs
index 3f697061b2..f83e7799cb 100644
--- a/src/Config/Converters/DataSourceConverterFactory.cs
+++ b/src/Config/Converters/DataSourceConverterFactory.cs
@@ -51,13 +51,16 @@ public DataSourceConverter(DeserializationVariableReplacementSettings? replaceme
string connectionString = string.Empty;
DatasourceHealthCheckConfig? health = null;
Dictionary? datasourceOptions = null;
- UserDelegatedAuthConfig? userDelegatedAuth = null;
+ UserDelegatedAuthOptions? userDelegatedAuth = null;
while (reader.Read())
{
if (reader.TokenType is JsonTokenType.EndObject)
{
- return new DataSource(databaseType, connectionString, datasourceOptions, health, userDelegatedAuth);
+ return new DataSource(databaseType, connectionString, datasourceOptions, health)
+ {
+ UserDelegatedAuth = userDelegatedAuth
+ };
}
if (reader.TokenType is JsonTokenType.PropertyName)
@@ -98,7 +101,7 @@ public DataSourceConverter(DeserializationVariableReplacementSettings? replaceme
{
try
{
- userDelegatedAuth = JsonSerializer.Deserialize(ref reader, options);
+ userDelegatedAuth = JsonSerializer.Deserialize(ref reader, options);
}
catch (Exception e)
{
diff --git a/src/Config/ObjectModel/DataSource.cs b/src/Config/ObjectModel/DataSource.cs
index 769e109a43..93982b5bd5 100644
--- a/src/Config/ObjectModel/DataSource.cs
+++ b/src/Config/ObjectModel/DataSource.cs
@@ -14,13 +14,11 @@ namespace Azure.DataApiBuilder.Config.ObjectModel;
/// Connection string to access the database.
/// Custom options for the specific database. If there are no options, this could be null.
/// Health check configuration for the datasource.
-/// User-delegated authentication configuration (OBO). Optional.
public record DataSource(
DatabaseType DatabaseType,
string ConnectionString,
Dictionary? Options = null,
- DatasourceHealthCheckConfig? Health = null,
- UserDelegatedAuthConfig? UserDelegatedAuth = null)
+ DatasourceHealthCheckConfig? Health = null)
{
[JsonIgnore]
public bool IsDatasourceHealthEnabled =>
@@ -42,6 +40,13 @@ public int DatasourceThresholdMs
}
}
+ ///
+ /// Configuration for user-delegated authentication (OBO) against the
+ /// configured database.
+ ///
+ [JsonPropertyName("user-delegated-auth")]
+ public UserDelegatedAuthOptions? UserDelegatedAuth { get; init; }
+
///
/// Converts the Options dictionary into a typed options object.
/// May return null if the dictionary is null.
diff --git a/src/Config/ObjectModel/UserDelegatedAuthConfig.cs b/src/Config/ObjectModel/UserDelegatedAuthConfig.cs
deleted file mode 100644
index ee086a4053..0000000000
--- a/src/Config/ObjectModel/UserDelegatedAuthConfig.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Text.Json.Serialization;
-
-namespace Azure.DataApiBuilder.Config.ObjectModel;
-
-///
-/// Configuration for user-delegated authentication (OBO - On-Behalf-Of).
-/// Enables per-user Entra ID access token authentication to Azure SQL.
-///
-/// Whether user-delegated authentication is enabled.
-/// The Azure SQL resource identifier for token acquisition.
-/// Explicitly control connection pooling behavior. Default: true (disabled) for safety. Connection pooling is disabled by default in OBO scenarios to prevent token reuse across different user contexts.
-/// In-memory cache duration for OBO tokens per user. Default: 50 minutes.
-public record UserDelegatedAuthConfig(
- bool Enabled = false,
- string? DatabaseAudience = null,
- bool? DisableConnectionPooling = null,
- int? TokenCacheDurationMinutes = null)
-{
- ///
- /// Default value for token cache duration in minutes.
- /// Must be less than typical token lifetime (60 min).
- ///
- public const int DEFAULT_TOKEN_CACHE_DURATION_MINUTES = 50;
-
- ///
- /// Default value for connection pooling (disabled for safety in MVP).
- ///
- public const bool DEFAULT_DISABLE_CONNECTION_POOLING = true;
-
- ///
- /// Minimum allowed token cache duration in minutes.
- ///
- public const int MIN_TOKEN_CACHE_DURATION_MINUTES = 1;
-
- ///
- /// Maximum allowed token cache duration in minutes.
- /// Must be less than typical token lifetime (60 min).
- ///
- public const int MAX_TOKEN_CACHE_DURATION_MINUTES = 59;
-
- ///
- /// Gets the effective token cache duration value.
- /// Returns the configured value or the default if not specified.
- ///
- [JsonIgnore]
- public int EffectiveTokenCacheDurationMinutes =>
- TokenCacheDurationMinutes ?? DEFAULT_TOKEN_CACHE_DURATION_MINUTES;
-
- ///
- /// Gets the effective connection pooling setting.
- /// Returns the configured value or the default if not specified.
- ///
- [JsonIgnore]
- public bool EffectiveDisableConnectionPooling =>
- DisableConnectionPooling ?? DEFAULT_DISABLE_CONNECTION_POOLING;
-}
diff --git a/src/Config/ObjectModel/UserDelegatedAuthOptions.cs b/src/Config/ObjectModel/UserDelegatedAuthOptions.cs
new file mode 100644
index 0000000000..fb3b3154e3
--- /dev/null
+++ b/src/Config/ObjectModel/UserDelegatedAuthOptions.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace Azure.DataApiBuilder.Config.ObjectModel;
+
+///
+/// Configuration for user-delegated authentication (OBO - On-Behalf-Of).
+/// Enables per-user Entra ID access token authentication to Azure SQL.
+///
+/// Whether user-delegated authentication is enabled.
+/// Identity provider for user-delegated authentication.
+/// The Azure SQL resource identifier for token acquisition.
+public record UserDelegatedAuthOptions(
+ [property: JsonPropertyName("enabled")] bool Enabled = false,
+ [property: JsonPropertyName("provider")] string? Provider = null,
+ [property: JsonPropertyName("database-audience")] string? DatabaseAudience = null)
+{
+ ///
+ /// Default duration, in minutes, to cache tokens for a given delegated identity.
+ /// With a 5-minute early refresh buffer, tokens are refreshed at the 40-minute mark.
+ ///
+ public const int DEFAULT_TOKEN_CACHE_DURATION_MINUTES = 45;
+
+ ///
+ /// Environment variable name for OBO App Registration client ID.
+ /// Uses DAB-specific prefix to avoid conflict with AZURE_CLIENT_ID which is
+ /// interpreted by DefaultAzureCredential/ManagedIdentityCredential as a
+ /// User-Assigned Managed Identity ID.
+ ///
+ public const string AZURE_CLIENT_ID_ENV_VAR = "DAB_OBO_CLIENT_ID";
+
+ ///
+ /// Environment variable name for OBO App Registration client secret.
+ /// Used for On-Behalf-Of token exchange.
+ ///
+ public const string AZURE_CLIENT_SECRET_ENV_VAR = "DAB_OBO_CLIENT_SECRET";
+
+ ///
+ /// Environment variable name for OBO tenant ID.
+ /// Uses DAB-specific prefix for consistency with OBO client ID.
+ ///
+ public const string AZURE_TENANT_ID_ENV_VAR = "DAB_OBO_TENANT_ID";
+}
From 4424cee335e7eb77564900f5dfb6ebfb7b7ddea0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 20:49:29 +0000
Subject: [PATCH 06/13] Revert global.json to original version
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
global.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/global.json b/global.json
index 1bdb496ef0..17811390a4 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.417",
+ "version": "8.0.418",
"rollForward": "latestFeature"
}
}
From 7d74d474a2c0937616f0eb40bec1e9705b84e559 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Feb 2026 00:51:08 +0000
Subject: [PATCH 07/13] Remove duplicate UserDelegatedAuthOptions.cs and
restore base definitions
- Delete separate UserDelegatedAuthOptions.cs file (already defined in DataSource.cs in base branch)
- Add missing IsUserDelegatedAuthEnabled property to DataSource
- Add missing UserDelegatedAuthOptions record definition to DataSource.cs
- Restore DataSourceConverterFactory.cs to base version (already has user-delegated-auth support)
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
src/Config/ObjectModel/DataSource.cs | 71 +++++++++++++++++++
.../ObjectModel/UserDelegatedAuthOptions.cs | 45 ------------
2 files changed, 71 insertions(+), 45 deletions(-)
delete mode 100644 src/Config/ObjectModel/UserDelegatedAuthOptions.cs
diff --git a/src/Config/ObjectModel/DataSource.cs b/src/Config/ObjectModel/DataSource.cs
index 93982b5bd5..e04acdfa37 100644
--- a/src/Config/ObjectModel/DataSource.cs
+++ b/src/Config/ObjectModel/DataSource.cs
@@ -47,6 +47,13 @@ public int DatasourceThresholdMs
[JsonPropertyName("user-delegated-auth")]
public UserDelegatedAuthOptions? UserDelegatedAuth { get; init; }
+ ///
+ /// Indicates whether user-delegated authentication is enabled for this data source.
+ ///
+ [JsonIgnore]
+ public bool IsUserDelegatedAuthEnabled =>
+ UserDelegatedAuth is not null && UserDelegatedAuth.Enabled;
+
///
/// Converts the Options dictionary into a typed options object.
/// May return null if the dictionary is null.
@@ -118,3 +125,67 @@ public record CosmosDbNoSQLDataSourceOptions(string? Database, string? Container
/// Options for MsSql database.
///
public record MsSqlOptions(bool SetSessionContext = true) : IDataSourceOptions;
+
+///
+/// Options for user-delegated authentication (OBO) for a data source.
+///
+/// When OBO is NOT enabled (default): DAB connects to the database using a single application principal,
+/// either via Managed Identity or credentials supplied in the connection string. All requests execute
+/// under the same database identity regardless of which user made the API call.
+///
+/// When OBO IS enabled: DAB exchanges the calling user's JWT for a database access token using the
+/// On-Behalf-Of flow. This allows DAB to connect to the database as the actual user, enabling
+/// Row-Level Security (RLS) filtering based on user identity.
+///
+/// OBO requires an Azure AD App Registration (separate from the DAB service's Managed Identity).
+/// The operator deploying DAB must set the following environment variables for the OBO App Registration,
+/// which DAB reads at startup via Environment.GetEnvironmentVariable():
+/// - DAB_OBO_CLIENT_ID: The Application (client) ID of the OBO App Registration
+/// - DAB_OBO_TENANT_ID: The Directory (tenant) ID where the OBO App Registration is registered
+/// - DAB_OBO_CLIENT_SECRET: The client secret of the OBO App Registration (not a user secret)
+///
+/// These credentials belong to the OBO App Registration, which acts as a confidential client to exchange
+/// the incoming user JWT for a database access token. The user provides only their JWT; DAB uses the
+/// App Registration credentials to perform the OBO token exchange on their behalf.
+///
+/// These can be set in the hosting environment (e.g., Azure Container Apps secrets, Kubernetes secrets,
+/// Docker environment variables, or local shell environment).
+///
+/// Note: DAB-specific prefixes (DAB_OBO_*) are used instead of AZURE_* to avoid conflict with
+/// DefaultAzureCredential, which interprets AZURE_CLIENT_ID as a User-Assigned Managed Identity ID.
+/// At startup (when no user context is available), DAB falls back to Managed Identity for metadata operations.
+///
+/// Whether user-delegated authentication is enabled.
+/// The authentication provider (currently only EntraId is supported).
+/// Audience used when acquiring database tokens on behalf of the user.
+public record UserDelegatedAuthOptions(
+ [property: JsonPropertyName("enabled")] bool Enabled = false,
+ [property: JsonPropertyName("provider")] string? Provider = null,
+ [property: JsonPropertyName("database-audience")] string? DatabaseAudience = null)
+{
+ ///
+ /// Default duration, in minutes, to cache tokens for a given delegated identity.
+ /// With a 5-minute early refresh buffer, tokens are refreshed at the 40-minute mark.
+ ///
+ public const int DEFAULT_TOKEN_CACHE_DURATION_MINUTES = 45;
+
+ ///
+ /// Environment variable name for OBO App Registration client ID.
+ /// Uses DAB-specific prefix to avoid conflict with AZURE_CLIENT_ID which is
+ /// interpreted by DefaultAzureCredential/ManagedIdentityCredential as a
+ /// User-Assigned Managed Identity ID.
+ ///
+ public const string DAB_OBO_CLIENT_ID_ENV_VAR = "DAB_OBO_CLIENT_ID";
+
+ ///
+ /// Environment variable name for OBO App Registration client secret.
+ /// Used for On-Behalf-Of token exchange.
+ ///
+ public const string DAB_OBO_CLIENT_SECRET_ENV_VAR = "DAB_OBO_CLIENT_SECRET";
+
+ ///
+ /// Environment variable name for OBO tenant ID.
+ /// Uses DAB-specific prefix for consistency with OBO client ID.
+ ///
+ public const string DAB_OBO_TENANT_ID_ENV_VAR = "DAB_OBO_TENANT_ID";
+}
diff --git a/src/Config/ObjectModel/UserDelegatedAuthOptions.cs b/src/Config/ObjectModel/UserDelegatedAuthOptions.cs
deleted file mode 100644
index fb3b3154e3..0000000000
--- a/src/Config/ObjectModel/UserDelegatedAuthOptions.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Text.Json.Serialization;
-
-namespace Azure.DataApiBuilder.Config.ObjectModel;
-
-///
-/// Configuration for user-delegated authentication (OBO - On-Behalf-Of).
-/// Enables per-user Entra ID access token authentication to Azure SQL.
-///
-/// Whether user-delegated authentication is enabled.
-/// Identity provider for user-delegated authentication.
-/// The Azure SQL resource identifier for token acquisition.
-public record UserDelegatedAuthOptions(
- [property: JsonPropertyName("enabled")] bool Enabled = false,
- [property: JsonPropertyName("provider")] string? Provider = null,
- [property: JsonPropertyName("database-audience")] string? DatabaseAudience = null)
-{
- ///
- /// Default duration, in minutes, to cache tokens for a given delegated identity.
- /// With a 5-minute early refresh buffer, tokens are refreshed at the 40-minute mark.
- ///
- public const int DEFAULT_TOKEN_CACHE_DURATION_MINUTES = 45;
-
- ///
- /// Environment variable name for OBO App Registration client ID.
- /// Uses DAB-specific prefix to avoid conflict with AZURE_CLIENT_ID which is
- /// interpreted by DefaultAzureCredential/ManagedIdentityCredential as a
- /// User-Assigned Managed Identity ID.
- ///
- public const string AZURE_CLIENT_ID_ENV_VAR = "DAB_OBO_CLIENT_ID";
-
- ///
- /// Environment variable name for OBO App Registration client secret.
- /// Used for On-Behalf-Of token exchange.
- ///
- public const string AZURE_CLIENT_SECRET_ENV_VAR = "DAB_OBO_CLIENT_SECRET";
-
- ///
- /// Environment variable name for OBO tenant ID.
- /// Uses DAB-specific prefix for consistency with OBO client ID.
- ///
- public const string AZURE_TENANT_ID_ENV_VAR = "DAB_OBO_TENANT_ID";
-}
From 8b5ccfe34ccbd550cda027c39bd59a5e63016143 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Feb 2026 00:52:52 +0000
Subject: [PATCH 08/13] Restore Config files to base - no changes needed
- DataSourceConverterFactory.cs already has user-delegated-auth support in base
- DataApiBuilderException.cs no changes needed
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
.../Converters/DataSourceConverterFactory.cs | 28 +++++++++----------
src/Config/DataApiBuilderException.cs | 11 +++++++-
2 files changed, 24 insertions(+), 15 deletions(-)
diff --git a/src/Config/Converters/DataSourceConverterFactory.cs b/src/Config/Converters/DataSourceConverterFactory.cs
index f83e7799cb..62d37c55e2 100644
--- a/src/Config/Converters/DataSourceConverterFactory.cs
+++ b/src/Config/Converters/DataSourceConverterFactory.cs
@@ -95,20 +95,6 @@ public DataSourceConverter(DeserializationVariableReplacementSettings? replaceme
}
}
- break;
- case "user-delegated-auth":
- if (reader.TokenType is not JsonTokenType.Null)
- {
- try
- {
- userDelegatedAuth = JsonSerializer.Deserialize(ref reader, options);
- }
- catch (Exception e)
- {
- throw new JsonException($"Error while deserializing DataSource user-delegated-auth: {e.Message}");
- }
- }
-
break;
case "options":
if (reader.TokenType is not JsonTokenType.Null)
@@ -154,6 +140,20 @@ public DataSourceConverter(DeserializationVariableReplacementSettings? replaceme
datasourceOptions = optionsDict;
}
+ break;
+ case "user-delegated-auth":
+ if (reader.TokenType != JsonTokenType.Null)
+ {
+ try
+ {
+ userDelegatedAuth = JsonSerializer.Deserialize(ref reader, options);
+ }
+ catch (Exception e)
+ {
+ throw new JsonException($"Error while deserializing DataSource user-delegated-auth: {e.Message}");
+ }
+ }
+
break;
default:
throw new JsonException($"Unexpected property {propertyName} while deserializing DataSource.");
diff --git a/src/Config/DataApiBuilderException.cs b/src/Config/DataApiBuilderException.cs
index b7696c4deb..95fe916c75 100644
--- a/src/Config/DataApiBuilderException.cs
+++ b/src/Config/DataApiBuilderException.cs
@@ -20,6 +20,11 @@ public class DataApiBuilderException : Exception
public const string GRAPHQL_MUTATION_FIELD_AUTHZ_FAILURE = "Unauthorized due to one or more fields in this mutation.";
public const string GRAPHQL_GROUPBY_FIELD_AUTHZ_FAILURE = "Access forbidden to field '{0}' referenced in the groupBy argument.";
public const string GRAPHQL_AGGREGATION_FIELD_AUTHZ_FAILURE = "Access forbidden to field '{0}' referenced in the aggregation function '{1}'.";
+ public const string OBO_IDENTITY_CLAIMS_MISSING = "User-delegated authentication failed: Neither 'oid' nor 'sub' claim found in the access token.";
+ public const string OBO_TENANT_CLAIM_MISSING = "User-delegated authentication failed: 'tid' (tenant id) claim not found in the access token.";
+ public const string OBO_TOKEN_ACQUISITION_FAILED = "User-delegated authentication failed: Unable to acquire database access token on behalf of the user.";
+ public const string OBO_MISSING_USER_CONTEXT = "User-delegated authentication failed: Missing or invalid 'Authorization: Bearer ' header. OBO requires a valid user token to exchange for database access.";
+ public const string OBO_MISSING_DATABASE_AUDIENCE = "User-delegated authentication failed: 'database-audience' is not configured in the data source's user-delegated-auth settings.";
public enum SubStatusCodes
{
@@ -127,7 +132,11 @@ public enum SubStatusCodes
///
/// Error due to client input validation failure.
///
- DatabaseInputError
+ DatabaseInputError,
+ ///
+ /// User-delegated (OBO) authentication failed due to missing identity claims.
+ ///
+ OboAuthenticationFailure
}
public HttpStatusCode StatusCode { get; }
From 70f32f13bac2854a425f93266607a9ee4eafb99f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Feb 2026 01:59:13 +0000
Subject: [PATCH 09/13] Add JSON structure validation tests for CLI
user-delegated-auth commands
- Add test to verify correct JSON structure in data-source.user-delegated-auth section
- Add test to verify JSON fields are updated correctly when modifying config
- Add test to verify disabling user-delegated-auth updates JSON properly
- All 15 tests passing (12 original + 3 new JSON validation tests)
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
src/Cli.Tests/ConfigureOptionsTests.cs | 185 +++++++++++++++++++++++++
1 file changed, 185 insertions(+)
diff --git a/src/Cli.Tests/ConfigureOptionsTests.cs b/src/Cli.Tests/ConfigureOptionsTests.cs
index 8ab024ae0b..46cf812618 100644
--- a/src/Cli.Tests/ConfigureOptionsTests.cs
+++ b/src/Cli.Tests/ConfigureOptionsTests.cs
@@ -1275,5 +1275,190 @@ public void TestUpdateUserDelegatedAuthDatabaseAudience()
Assert.IsTrue(config.DataSource.UserDelegatedAuth.Enabled);
Assert.AreEqual(newAudience, config.DataSource.UserDelegatedAuth.DatabaseAudience);
}
+
+ ///
+ /// Tests that CLI commands write the correct JSON structure to dab-config.json.
+ /// Verifies that user-delegated-auth section is properly nested under data-source with correct property names.
+ ///
+ [TestMethod]
+ public void TestUserDelegatedAuthCreatesCorrectJsonStructure()
+ {
+ // Arrange
+ SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
+ string audienceValue = "https://database.windows.net";
+
+ ConfigureOptions options = new(
+ dataSourceUserDelegatedAuthEnabled: true,
+ dataSourceUserDelegatedAuthDatabaseAudience: audienceValue,
+ config: TEST_RUNTIME_CONFIG_FILE
+ );
+
+ // Act
+ bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
+
+ // Assert
+ Assert.IsTrue(isSuccess);
+ string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
+
+ // Verify JSON structure using JObject
+ JObject configJson = JObject.Parse(updatedConfig);
+
+ // Verify data-source exists
+ Assert.IsNotNull(configJson["data-source"]);
+
+ // Verify user-delegated-auth section exists under data-source
+ JToken? userDelegatedAuthSection = configJson["data-source"]?["user-delegated-auth"];
+ Assert.IsNotNull(userDelegatedAuthSection);
+
+ // Verify correct properties with correct values
+ Assert.AreEqual(true, (bool?)userDelegatedAuthSection["enabled"]);
+ Assert.AreEqual("EntraId", (string?)userDelegatedAuthSection["provider"]);
+ Assert.AreEqual(audienceValue, (string?)userDelegatedAuthSection["database-audience"]);
+
+ // Verify no unexpected properties
+ JObject userDelegatedAuthObj = (JObject)userDelegatedAuthSection;
+ Assert.AreEqual(3, userDelegatedAuthObj.Properties().Count());
+ }
+
+ ///
+ /// Tests that CLI correctly updates the JSON structure when modifying existing user-delegated-auth configuration.
+ /// Verifies that only the specified field is updated while others are preserved.
+ ///
+ [TestMethod]
+ public void TestUserDelegatedAuthUpdatesCorrectJsonFields()
+ {
+ // Arrange - Start with config that has user-delegated-auth
+ string configWithUserDelegatedAuth = @"
+ {
+ ""$schema"": ""test"",
+ ""data-source"": {
+ ""database-type"": ""mssql"",
+ ""connection-string"": ""testconnectionstring"",
+ ""user-delegated-auth"": {
+ ""enabled"": true,
+ ""provider"": ""EntraId"",
+ ""database-audience"": ""https://database.windows.net""
+ }
+ },
+ ""runtime"": {
+ ""rest"": {
+ ""enabled"": true,
+ ""path"": ""/api""
+ },
+ ""graphql"": {
+ ""enabled"": true,
+ ""path"": ""/graphql"",
+ ""allow-introspection"": true
+ },
+ ""host"": {
+ ""mode"": ""development"",
+ ""cors"": {
+ ""origins"": [],
+ ""allow-credentials"": false
+ },
+ ""authentication"": {
+ ""provider"": ""StaticWebApps""
+ }
+ }
+ },
+ ""entities"": {}
+ }";
+ SetupFileSystemWithInitialConfig(configWithUserDelegatedAuth);
+
+ string newAudience = "https://database.chinacloudapi.cn";
+ ConfigureOptions options = new(
+ dataSourceUserDelegatedAuthDatabaseAudience: newAudience,
+ config: TEST_RUNTIME_CONFIG_FILE
+ );
+
+ // Act
+ bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
+
+ // Assert
+ Assert.IsTrue(isSuccess);
+ string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
+
+ // Verify JSON structure
+ JObject configJson = JObject.Parse(updatedConfig);
+ JToken? userDelegatedAuthSection = configJson["data-source"]?["user-delegated-auth"];
+ Assert.IsNotNull(userDelegatedAuthSection);
+
+ // Verify database-audience was updated
+ Assert.AreEqual(newAudience, (string?)userDelegatedAuthSection["database-audience"]);
+
+ // Verify other fields were preserved
+ Assert.AreEqual(true, (bool?)userDelegatedAuthSection["enabled"]);
+ Assert.AreEqual("EntraId", (string?)userDelegatedAuthSection["provider"]);
+ }
+
+ ///
+ /// Tests that disabling user-delegated-auth updates the JSON structure correctly.
+ /// Verifies that the enabled field can be set to false while preserving other settings.
+ ///
+ [TestMethod]
+ public void TestUserDelegatedAuthDisableUpdatesJsonCorrectly()
+ {
+ // Arrange - Start with enabled config
+ string configWithUserDelegatedAuth = @"
+ {
+ ""$schema"": ""test"",
+ ""data-source"": {
+ ""database-type"": ""mssql"",
+ ""connection-string"": ""testconnectionstring"",
+ ""user-delegated-auth"": {
+ ""enabled"": true,
+ ""provider"": ""EntraId"",
+ ""database-audience"": ""https://database.windows.net""
+ }
+ },
+ ""runtime"": {
+ ""rest"": {
+ ""enabled"": true,
+ ""path"": ""/api""
+ },
+ ""graphql"": {
+ ""enabled"": true,
+ ""path"": ""/graphql"",
+ ""allow-introspection"": true
+ },
+ ""host"": {
+ ""mode"": ""development"",
+ ""cors"": {
+ ""origins"": [],
+ ""allow-credentials"": false
+ },
+ ""authentication"": {
+ ""provider"": ""StaticWebApps""
+ }
+ }
+ },
+ ""entities"": {}
+ }";
+ SetupFileSystemWithInitialConfig(configWithUserDelegatedAuth);
+
+ ConfigureOptions options = new(
+ dataSourceUserDelegatedAuthEnabled: false,
+ config: TEST_RUNTIME_CONFIG_FILE
+ );
+
+ // Act
+ bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
+
+ // Assert
+ Assert.IsTrue(isSuccess);
+ string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
+
+ // Verify JSON structure
+ JObject configJson = JObject.Parse(updatedConfig);
+ JToken? userDelegatedAuthSection = configJson["data-source"]?["user-delegated-auth"];
+ Assert.IsNotNull(userDelegatedAuthSection);
+
+ // Verify enabled was set to false
+ Assert.AreEqual(false, (bool?)userDelegatedAuthSection["enabled"]);
+
+ // Verify other fields were preserved
+ Assert.AreEqual("EntraId", (string?)userDelegatedAuthSection["provider"]);
+ Assert.AreEqual("https://database.windows.net", (string?)userDelegatedAuthSection["database-audience"]);
+ }
}
}
From c0795d3a323a275e2d4f0ae458b4e1a94fd1d8e7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Feb 2026 02:05:19 +0000
Subject: [PATCH 10/13] Refactor: Extract duplicate JSON config to TestHelper
constant
- Add CONFIG_WITH_USER_DELEGATED_AUTH constant to TestHelper
- Use constant in test methods to reduce duplication
- Improves test maintainability
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
src/Cli.Tests/ConfigureOptionsTests.cs | 110 +------------------------
src/Cli.Tests/TestHelper.cs | 40 +++++++++
2 files changed, 43 insertions(+), 107 deletions(-)
diff --git a/src/Cli.Tests/ConfigureOptionsTests.cs b/src/Cli.Tests/ConfigureOptionsTests.cs
index 46cf812618..a82af009a9 100644
--- a/src/Cli.Tests/ConfigureOptionsTests.cs
+++ b/src/Cli.Tests/ConfigureOptionsTests.cs
@@ -1221,41 +1221,7 @@ public void TestFailureWhenEnablingUserDelegatedAuthOnNonMSSQLDatabase(string db
public void TestUpdateUserDelegatedAuthDatabaseAudience()
{
// Arrange - Config with existing user-delegated-auth section
- string configWithUserDelegatedAuth = @"
- {
- ""$schema"": ""test"",
- ""data-source"": {
- ""database-type"": ""mssql"",
- ""connection-string"": ""testconnectionstring"",
- ""user-delegated-auth"": {
- ""enabled"": true,
- ""database-audience"": ""https://database.windows.net""
- }
- },
- ""runtime"": {
- ""rest"": {
- ""enabled"": true,
- ""path"": ""/api""
- },
- ""graphql"": {
- ""enabled"": true,
- ""path"": ""/graphql"",
- ""allow-introspection"": true
- },
- ""host"": {
- ""mode"": ""development"",
- ""cors"": {
- ""origins"": [],
- ""allow-credentials"": false
- },
- ""authentication"": {
- ""provider"": ""StaticWebApps""
- }
- }
- },
- ""entities"": {}
- }";
- SetupFileSystemWithInitialConfig(configWithUserDelegatedAuth);
+ SetupFileSystemWithInitialConfig(TestHelper.CONFIG_WITH_USER_DELEGATED_AUTH);
string newAudience = "https://database.usgovcloudapi.net";
ConfigureOptions options = new(
@@ -1328,42 +1294,7 @@ public void TestUserDelegatedAuthCreatesCorrectJsonStructure()
public void TestUserDelegatedAuthUpdatesCorrectJsonFields()
{
// Arrange - Start with config that has user-delegated-auth
- string configWithUserDelegatedAuth = @"
- {
- ""$schema"": ""test"",
- ""data-source"": {
- ""database-type"": ""mssql"",
- ""connection-string"": ""testconnectionstring"",
- ""user-delegated-auth"": {
- ""enabled"": true,
- ""provider"": ""EntraId"",
- ""database-audience"": ""https://database.windows.net""
- }
- },
- ""runtime"": {
- ""rest"": {
- ""enabled"": true,
- ""path"": ""/api""
- },
- ""graphql"": {
- ""enabled"": true,
- ""path"": ""/graphql"",
- ""allow-introspection"": true
- },
- ""host"": {
- ""mode"": ""development"",
- ""cors"": {
- ""origins"": [],
- ""allow-credentials"": false
- },
- ""authentication"": {
- ""provider"": ""StaticWebApps""
- }
- }
- },
- ""entities"": {}
- }";
- SetupFileSystemWithInitialConfig(configWithUserDelegatedAuth);
+ SetupFileSystemWithInitialConfig(TestHelper.CONFIG_WITH_USER_DELEGATED_AUTH);
string newAudience = "https://database.chinacloudapi.cn";
ConfigureOptions options = new(
@@ -1399,42 +1330,7 @@ public void TestUserDelegatedAuthUpdatesCorrectJsonFields()
public void TestUserDelegatedAuthDisableUpdatesJsonCorrectly()
{
// Arrange - Start with enabled config
- string configWithUserDelegatedAuth = @"
- {
- ""$schema"": ""test"",
- ""data-source"": {
- ""database-type"": ""mssql"",
- ""connection-string"": ""testconnectionstring"",
- ""user-delegated-auth"": {
- ""enabled"": true,
- ""provider"": ""EntraId"",
- ""database-audience"": ""https://database.windows.net""
- }
- },
- ""runtime"": {
- ""rest"": {
- ""enabled"": true,
- ""path"": ""/api""
- },
- ""graphql"": {
- ""enabled"": true,
- ""path"": ""/graphql"",
- ""allow-introspection"": true
- },
- ""host"": {
- ""mode"": ""development"",
- ""cors"": {
- ""origins"": [],
- ""allow-credentials"": false
- },
- ""authentication"": {
- ""provider"": ""StaticWebApps""
- }
- }
- },
- ""entities"": {}
- }";
- SetupFileSystemWithInitialConfig(configWithUserDelegatedAuth);
+ SetupFileSystemWithInitialConfig(TestHelper.CONFIG_WITH_USER_DELEGATED_AUTH);
ConfigureOptions options = new(
dataSourceUserDelegatedAuthEnabled: false,
diff --git a/src/Cli.Tests/TestHelper.cs b/src/Cli.Tests/TestHelper.cs
index 8224a079d4..1cd7852de9 100644
--- a/src/Cli.Tests/TestHelper.cs
+++ b/src/Cli.Tests/TestHelper.cs
@@ -279,6 +279,46 @@ public static Process ExecuteDabCommand(string command, string flags)
public const string CONFIG_WITH_DISABLED_GLOBAL_REST_GRAPHQL = $"{{{SAMPLE_SCHEMA_DATA_SOURCE},{RUNTIME_SECTION_WITH_DISABLED_REST_GRAPHQL}}}";
+ ///
+ /// A config json with user-delegated-auth enabled. This is used in tests to verify updating existing
+ /// user-delegated-auth configuration.
+ ///
+ public const string CONFIG_WITH_USER_DELEGATED_AUTH = @"
+ {
+ ""$schema"": """ + DAB_DRAFT_SCHEMA_TEST_PATH + @""",
+ ""data-source"": {
+ ""database-type"": ""mssql"",
+ ""connection-string"": """ + SAMPLE_TEST_CONN_STRING + @""",
+ ""user-delegated-auth"": {
+ ""enabled"": true,
+ ""provider"": ""EntraId"",
+ ""database-audience"": ""https://database.windows.net""
+ }
+ },
+ ""runtime"": {
+ ""rest"": {
+ ""enabled"": true,
+ ""path"": ""/api""
+ },
+ ""graphql"": {
+ ""enabled"": true,
+ ""path"": ""/graphql"",
+ ""allow-introspection"": true
+ },
+ ""host"": {
+ ""mode"": ""development"",
+ ""cors"": {
+ ""origins"": [],
+ ""allow-credentials"": false
+ },
+ ""authentication"": {
+ ""provider"": ""StaticWebApps""
+ }
+ }
+ },
+ ""entities"": {}
+ }";
+
public const string SINGLE_ENTITY = @"
{
""entities"": {
From 1cd2637c6ff2b809322482c95e3ae471e28c3a15 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Feb 2026 17:04:36 +0000
Subject: [PATCH 11/13] Address code review feedback - consolidate tests and
update help text
- Update help text to mention SQL Server on-premises (not just Azure SQL)
- Consolidate three separate tests into one parameterized test with DataRows
- Remove redundant JSON validation tests (parsing tests already verify properties)
- Add JSON structure validation to TestUpdateUserDelegatedAuthDatabaseAudience
- Reduced from 15 to 12 tests while maintaining coverage
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
src/Cli.Tests/ConfigureOptionsTests.cs | 204 ++++---------------------
src/Cli/Commands/ConfigureOptions.cs | 4 +-
2 files changed, 32 insertions(+), 176 deletions(-)
diff --git a/src/Cli.Tests/ConfigureOptionsTests.cs b/src/Cli.Tests/ConfigureOptionsTests.cs
index a82af009a9..b1bf3ba303 100644
--- a/src/Cli.Tests/ConfigureOptionsTests.cs
+++ b/src/Cli.Tests/ConfigureOptionsTests.cs
@@ -1096,46 +1096,27 @@ private void SetupFileSystemWithInitialConfig(string jsonConfig)
}
///
- /// Tests adding user-delegated-auth.enabled to a config that doesn't have user-delegated-auth configured.
- /// This method verifies that the user-delegated-auth.enabled property can be set to true for MSSQL database.
- /// Command: dab configure --data-source.user-delegated-auth.enabled true
+ /// Tests adding user-delegated-auth configuration options individually or together.
+ /// Verifies that enabled and database-audience properties can be set independently or combined.
+ /// Commands:
+ /// - dab configure --data-source.user-delegated-auth.enabled true
+ /// - dab configure --data-source.user-delegated-auth.database-audience "https://database.windows.net"
+ /// - dab configure --data-source.user-delegated-auth.enabled true --data-source.user-delegated-auth.database-audience "https://database.windows.net"
///
- [TestMethod]
- public void TestAddUserDelegatedAuthEnabled()
- {
- // Arrange
- SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
-
- ConfigureOptions options = new(
- dataSourceUserDelegatedAuthEnabled: true,
- config: TEST_RUNTIME_CONFIG_FILE
- );
-
- // Act
- bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
-
- // Assert
- Assert.IsTrue(isSuccess);
- string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
- Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config));
- Assert.IsNotNull(config.DataSource);
- Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
- Assert.IsTrue(config.DataSource.UserDelegatedAuth.Enabled);
- }
-
- ///
- /// Tests adding user-delegated-auth.database-audience to a config that doesn't have user-delegated-auth configured.
- /// This method verifies that the database-audience can be set for user-delegated authentication.
- /// Command: dab configure --data-source.user-delegated-auth.database-audience "https://database.windows.net"
- ///
- [TestMethod]
- public void TestAddUserDelegatedAuthDatabaseAudience()
+ [DataTestMethod]
+ [DataRow(true, null, DisplayName = "Set enabled=true only")]
+ [DataRow(null, "https://database.windows.net", DisplayName = "Set database-audience only")]
+ [DataRow(true, "https://database.windows.net", DisplayName = "Set both enabled and database-audience (public cloud)")]
+ [DataRow(true, "https://database.usgovcloudapi.net", DisplayName = "Set both enabled and database-audience (gov cloud)")]
+ [DataRow(true, "https://database.chinacloudapi.cn", DisplayName = "Set both enabled and database-audience (china cloud)")]
+ [DataRow(true, "https://myinstance.abc123.database.windows.net", DisplayName = "Set both enabled and database-audience (managed instance)")]
+ public void TestAddUserDelegatedAuthConfiguration(bool? enabledValue, string? audienceValue)
{
// Arrange
SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
- string audienceValue = "https://database.windows.net";
ConfigureOptions options = new(
+ dataSourceUserDelegatedAuthEnabled: enabledValue,
dataSourceUserDelegatedAuthDatabaseAudience: audienceValue,
config: TEST_RUNTIME_CONFIG_FILE
);
@@ -1149,41 +1130,21 @@ public void TestAddUserDelegatedAuthDatabaseAudience()
Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config));
Assert.IsNotNull(config.DataSource);
Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
- Assert.AreEqual(audienceValue, config.DataSource.UserDelegatedAuth.DatabaseAudience);
- }
- ///
- /// Tests adding both user-delegated-auth.enabled and database-audience in a single command.
- /// This method verifies that both properties can be set together.
- /// Command: dab configure --data-source.user-delegated-auth.enabled true --data-source.user-delegated-auth.database-audience "https://database.windows.net"
- ///
- [DataTestMethod]
- [DataRow("https://database.windows.net", DisplayName = "Azure SQL Database (public cloud)")]
- [DataRow("https://database.usgovcloudapi.net", DisplayName = "Azure Government Cloud")]
- [DataRow("https://database.chinacloudapi.cn", DisplayName = "Azure China Cloud")]
- [DataRow("https://myinstance.abc123.database.windows.net", DisplayName = "Azure SQL Managed Instance")]
- public void TestAddUserDelegatedAuthEnabledAndDatabaseAudience(string audienceValue)
- {
- // Arrange
- SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
-
- ConfigureOptions options = new(
- dataSourceUserDelegatedAuthEnabled: true,
- dataSourceUserDelegatedAuthDatabaseAudience: audienceValue,
- config: TEST_RUNTIME_CONFIG_FILE
- );
+ // Verify enabled value
+ if (enabledValue.HasValue)
+ {
+ Assert.AreEqual(enabledValue.Value, config.DataSource.UserDelegatedAuth.Enabled);
+ }
- // Act
- bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
+ // Verify database-audience value
+ if (audienceValue is not null)
+ {
+ Assert.AreEqual(audienceValue, config.DataSource.UserDelegatedAuth.DatabaseAudience);
+ }
- // Assert
- Assert.IsTrue(isSuccess);
- string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
- Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config));
- Assert.IsNotNull(config.DataSource);
- Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
- Assert.IsTrue(config.DataSource.UserDelegatedAuth.Enabled);
- Assert.AreEqual(audienceValue, config.DataSource.UserDelegatedAuth.DatabaseAudience);
+ // Verify provider is set to default
+ Assert.AreEqual("EntraId", config.DataSource.UserDelegatedAuth.Provider);
}
///
@@ -1216,6 +1177,7 @@ public void TestFailureWhenEnablingUserDelegatedAuthOnNonMSSQLDatabase(string db
///
/// Tests updating existing user-delegated-auth configuration by changing the database-audience.
/// This method verifies that the database-audience can be updated while preserving the enabled setting.
+ /// Also verifies that the JSON structure is correct with proper nesting under data-source.
///
[TestMethod]
public void TestUpdateUserDelegatedAuthDatabaseAudience()
@@ -1240,121 +1202,15 @@ public void TestUpdateUserDelegatedAuthDatabaseAudience()
Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
Assert.IsTrue(config.DataSource.UserDelegatedAuth.Enabled);
Assert.AreEqual(newAudience, config.DataSource.UserDelegatedAuth.DatabaseAudience);
- }
-
- ///
- /// Tests that CLI commands write the correct JSON structure to dab-config.json.
- /// Verifies that user-delegated-auth section is properly nested under data-source with correct property names.
- ///
- [TestMethod]
- public void TestUserDelegatedAuthCreatesCorrectJsonStructure()
- {
- // Arrange
- SetupFileSystemWithInitialConfig(INITIAL_CONFIG);
- string audienceValue = "https://database.windows.net";
+ Assert.AreEqual("EntraId", config.DataSource.UserDelegatedAuth.Provider);
- ConfigureOptions options = new(
- dataSourceUserDelegatedAuthEnabled: true,
- dataSourceUserDelegatedAuthDatabaseAudience: audienceValue,
- config: TEST_RUNTIME_CONFIG_FILE
- );
-
- // Act
- bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
-
- // Assert
- Assert.IsTrue(isSuccess);
- string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
-
- // Verify JSON structure using JObject
- JObject configJson = JObject.Parse(updatedConfig);
-
- // Verify data-source exists
- Assert.IsNotNull(configJson["data-source"]);
-
- // Verify user-delegated-auth section exists under data-source
- JToken? userDelegatedAuthSection = configJson["data-source"]?["user-delegated-auth"];
- Assert.IsNotNull(userDelegatedAuthSection);
-
- // Verify correct properties with correct values
- Assert.AreEqual(true, (bool?)userDelegatedAuthSection["enabled"]);
- Assert.AreEqual("EntraId", (string?)userDelegatedAuthSection["provider"]);
- Assert.AreEqual(audienceValue, (string?)userDelegatedAuthSection["database-audience"]);
-
- // Verify no unexpected properties
- JObject userDelegatedAuthObj = (JObject)userDelegatedAuthSection;
- Assert.AreEqual(3, userDelegatedAuthObj.Properties().Count());
- }
-
- ///
- /// Tests that CLI correctly updates the JSON structure when modifying existing user-delegated-auth configuration.
- /// Verifies that only the specified field is updated while others are preserved.
- ///
- [TestMethod]
- public void TestUserDelegatedAuthUpdatesCorrectJsonFields()
- {
- // Arrange - Start with config that has user-delegated-auth
- SetupFileSystemWithInitialConfig(TestHelper.CONFIG_WITH_USER_DELEGATED_AUTH);
-
- string newAudience = "https://database.chinacloudapi.cn";
- ConfigureOptions options = new(
- dataSourceUserDelegatedAuthDatabaseAudience: newAudience,
- config: TEST_RUNTIME_CONFIG_FILE
- );
-
- // Act
- bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
-
- // Assert
- Assert.IsTrue(isSuccess);
- string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
-
- // Verify JSON structure
+ // Verify JSON structure using JObject to ensure correct nesting
JObject configJson = JObject.Parse(updatedConfig);
JToken? userDelegatedAuthSection = configJson["data-source"]?["user-delegated-auth"];
Assert.IsNotNull(userDelegatedAuthSection);
-
- // Verify database-audience was updated
Assert.AreEqual(newAudience, (string?)userDelegatedAuthSection["database-audience"]);
-
- // Verify other fields were preserved
Assert.AreEqual(true, (bool?)userDelegatedAuthSection["enabled"]);
Assert.AreEqual("EntraId", (string?)userDelegatedAuthSection["provider"]);
}
-
- ///
- /// Tests that disabling user-delegated-auth updates the JSON structure correctly.
- /// Verifies that the enabled field can be set to false while preserving other settings.
- ///
- [TestMethod]
- public void TestUserDelegatedAuthDisableUpdatesJsonCorrectly()
- {
- // Arrange - Start with enabled config
- SetupFileSystemWithInitialConfig(TestHelper.CONFIG_WITH_USER_DELEGATED_AUTH);
-
- ConfigureOptions options = new(
- dataSourceUserDelegatedAuthEnabled: false,
- config: TEST_RUNTIME_CONFIG_FILE
- );
-
- // Act
- bool isSuccess = TryConfigureSettings(options, _runtimeConfigLoader!, _fileSystem!);
-
- // Assert
- Assert.IsTrue(isSuccess);
- string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
-
- // Verify JSON structure
- JObject configJson = JObject.Parse(updatedConfig);
- JToken? userDelegatedAuthSection = configJson["data-source"]?["user-delegated-auth"];
- Assert.IsNotNull(userDelegatedAuthSection);
-
- // Verify enabled was set to false
- Assert.AreEqual(false, (bool?)userDelegatedAuthSection["enabled"]);
-
- // Verify other fields were preserved
- Assert.AreEqual("EntraId", (string?)userDelegatedAuthSection["provider"]);
- Assert.AreEqual("https://database.windows.net", (string?)userDelegatedAuthSection["database-audience"]);
- }
}
}
diff --git a/src/Cli/Commands/ConfigureOptions.cs b/src/Cli/Commands/ConfigureOptions.cs
index 069a2d3b6a..262cbc9145 100644
--- a/src/Cli/Commands/ConfigureOptions.cs
+++ b/src/Cli/Commands/ConfigureOptions.cs
@@ -164,10 +164,10 @@ public ConfigureOptions(
[Option("data-source.health.name", Required = false, HelpText = "Identifier for data source in health check report.")]
public string? DataSourceHealthName { get; }
- [Option("data-source.user-delegated-auth.enabled", Required = false, HelpText = "Enable user-delegated authentication (OBO) for Azure SQL. Default: false (boolean).")]
+ [Option("data-source.user-delegated-auth.enabled", Required = false, HelpText = "Enable user-delegated authentication (OBO) for Azure SQL and SQL Server. Default: false (boolean).")]
public bool? DataSourceUserDelegatedAuthEnabled { get; }
- [Option("data-source.user-delegated-auth.database-audience", Required = false, HelpText = "Azure SQL resource identifier for token acquisition (e.g., https://database.windows.net).")]
+ [Option("data-source.user-delegated-auth.database-audience", Required = false, HelpText = "Database resource identifier for token acquisition (e.g., https://database.windows.net for Azure SQL).")]
public string? DataSourceUserDelegatedAuthDatabaseAudience { get; }
[Option("runtime.graphql.depth-limit", Required = false, HelpText = "Max allowed depth of the nested query. Allowed values: (0,2147483647] inclusive. Default is infinity. Use -1 to remove limit.")]
From c0fdd623f139af74533e7cd903991962af354c2a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Feb 2026 17:08:38 +0000
Subject: [PATCH 12/13] Enhance test assertions to verify default values
- Add assertions to verify default Enabled=false when only database-audience is set
- Add assertions to verify default DatabaseAudience=null when only enabled is set
- Improve method documentation to clarify JSON structure validation
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
src/Cli.Tests/ConfigureOptionsTests.cs | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/src/Cli.Tests/ConfigureOptionsTests.cs b/src/Cli.Tests/ConfigureOptionsTests.cs
index b1bf3ba303..e18b10468e 100644
--- a/src/Cli.Tests/ConfigureOptionsTests.cs
+++ b/src/Cli.Tests/ConfigureOptionsTests.cs
@@ -1098,6 +1098,7 @@ private void SetupFileSystemWithInitialConfig(string jsonConfig)
///
/// Tests adding user-delegated-auth configuration options individually or together.
/// Verifies that enabled and database-audience properties can be set independently or combined.
+ /// Also verifies default values for properties not explicitly set.
/// Commands:
/// - dab configure --data-source.user-delegated-auth.enabled true
/// - dab configure --data-source.user-delegated-auth.database-audience "https://database.windows.net"
@@ -1131,17 +1132,25 @@ public void TestAddUserDelegatedAuthConfiguration(bool? enabledValue, string? au
Assert.IsNotNull(config.DataSource);
Assert.IsNotNull(config.DataSource.UserDelegatedAuth);
- // Verify enabled value
+ // Verify enabled value (if set, use provided value; otherwise defaults to false)
if (enabledValue.HasValue)
{
Assert.AreEqual(enabledValue.Value, config.DataSource.UserDelegatedAuth.Enabled);
}
+ else
+ {
+ Assert.IsFalse(config.DataSource.UserDelegatedAuth.Enabled);
+ }
// Verify database-audience value
if (audienceValue is not null)
{
Assert.AreEqual(audienceValue, config.DataSource.UserDelegatedAuth.DatabaseAudience);
}
+ else
+ {
+ Assert.IsNull(config.DataSource.UserDelegatedAuth.DatabaseAudience);
+ }
// Verify provider is set to default
Assert.AreEqual("EntraId", config.DataSource.UserDelegatedAuth.Provider);
@@ -1176,8 +1185,9 @@ public void TestFailureWhenEnablingUserDelegatedAuthOnNonMSSQLDatabase(string db
///
/// Tests updating existing user-delegated-auth configuration by changing the database-audience.
- /// This method verifies that the database-audience can be updated while preserving the enabled setting.
- /// Also verifies that the JSON structure is correct with proper nesting under data-source.
+ /// Verifies that the database-audience can be updated while preserving the enabled setting.
+ /// Also validates JSON structure: verifies user-delegated-auth is correctly nested under data-source
+ /// with proper JSON property names (enabled, provider, database-audience).
///
[TestMethod]
public void TestUpdateUserDelegatedAuthDatabaseAudience()
From 098fbcc6e2a9a9ef70bab8d4b125c0321d6e5eac Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 27 Feb 2026 01:09:04 +0000
Subject: [PATCH 13/13] Remove redundant cloud-specific DataRows from tests
Removed duplicate DataRows for gov/china/managed instance clouds since they just
test string assignment and don't actually validate cloud endpoints. Kept essential
test cases: enabled only, audience only, and both together.
Test count reduced from 12 to 9 tests while maintaining full coverage.
Co-authored-by: anushakolan <45540936+anushakolan@users.noreply.github.com>
---
src/Cli.Tests/ConfigureOptionsTests.cs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/Cli.Tests/ConfigureOptionsTests.cs b/src/Cli.Tests/ConfigureOptionsTests.cs
index e18b10468e..a1355679fb 100644
--- a/src/Cli.Tests/ConfigureOptionsTests.cs
+++ b/src/Cli.Tests/ConfigureOptionsTests.cs
@@ -1107,10 +1107,7 @@ private void SetupFileSystemWithInitialConfig(string jsonConfig)
[DataTestMethod]
[DataRow(true, null, DisplayName = "Set enabled=true only")]
[DataRow(null, "https://database.windows.net", DisplayName = "Set database-audience only")]
- [DataRow(true, "https://database.windows.net", DisplayName = "Set both enabled and database-audience (public cloud)")]
- [DataRow(true, "https://database.usgovcloudapi.net", DisplayName = "Set both enabled and database-audience (gov cloud)")]
- [DataRow(true, "https://database.chinacloudapi.cn", DisplayName = "Set both enabled and database-audience (china cloud)")]
- [DataRow(true, "https://myinstance.abc123.database.windows.net", DisplayName = "Set both enabled and database-audience (managed instance)")]
+ [DataRow(true, "https://database.windows.net", DisplayName = "Set both enabled and database-audience")]
public void TestAddUserDelegatedAuthConfiguration(bool? enabledValue, string? audienceValue)
{
// Arrange