diff --git a/src/Cli.Tests/ConfigureOptionsTests.cs b/src/Cli.Tests/ConfigureOptionsTests.cs
index e0089eacfa..8ee064e262 100644
--- a/src/Cli.Tests/ConfigureOptionsTests.cs
+++ b/src/Cli.Tests/ConfigureOptionsTests.cs
@@ -512,6 +512,7 @@ public void TestUpdateCorsAllowCredentialsHostSettings(bool allowCredentialsValu
[DataRow("staticWebApps", DisplayName = "Update authentication.provider to StaticWebApps for Host.")]
[DataRow("Appservice", DisplayName = "Update authentication.provider to AppService for Host.")]
[DataRow("azuread", DisplayName = "Update authentication.provider to AzureAD for Host.")]
+ [DataRow("entraid", DisplayName = "Update authentication.provider to EntraID for Host.")]
public void TestUpdateAuthenticationProviderHostSettings(string authenticationProviderValue)
{
// Arrange -> all the setup which includes creating options.
diff --git a/src/Cli.Tests/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs
index c1dfcbf9ce..28fecfb2d5 100644
--- a/src/Cli.Tests/EndToEndTests.cs
+++ b/src/Cli.Tests/EndToEndTests.cs
@@ -459,11 +459,13 @@ public void TestUpdateCacheTtlRuntimeSettings(string ttl, bool isSuccess)
/// neither EasyAuth or Simulator as Authentication provider.
/// It checks correct generation of config with provider, audience and issuer.
///
- [TestMethod]
- public void TestVerifyAuthenticationOptions()
+ [DataTestMethod]
+ [DataRow("AzureAD")]
+ [DataRow("EntraID")]
+ public void TestVerifyAuthenticationOptions(string authenticationProvider)
{
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--database-type", "mssql",
- "--auth.provider", "AzureAD", "--auth.audience", "aud-xxx", "--auth.issuer", "issuer-xxx" };
+ "--auth.provider", authenticationProvider, "--auth.audience", "aud-xxx", "--auth.issuer", "issuer-xxx" };
Program.Execute(initArgs, _cliLogger!, _fileSystem!, _runtimeConfigLoader!);
Assert.IsTrue(_runtimeConfigLoader!.TryLoadConfig(TEST_RUNTIME_CONFIG_FILE, out RuntimeConfig? runtimeConfig));
@@ -471,7 +473,7 @@ public void TestVerifyAuthenticationOptions()
Assert.IsNotNull(runtimeConfig.Runtime);
Assert.IsNotNull(runtimeConfig.Runtime.Host);
- Assert.AreEqual("AzureAD", runtimeConfig.Runtime.Host.Authentication?.Provider);
+ Assert.AreEqual(authenticationProvider, runtimeConfig.Runtime.Host.Authentication?.Provider);
Assert.AreEqual("aud-xxx", runtimeConfig.Runtime.Host.Authentication?.Jwt?.Audience);
Assert.AreEqual("issuer-xxx", runtimeConfig.Runtime.Host.Authentication?.Jwt?.Issuer);
}
@@ -1114,6 +1116,7 @@ public async Task TestExitOfRuntimeEngineWithInvalidConfig(
[DataRow("StaticWebApps", false)]
[DataRow("AppService", true)]
[DataRow("AzureAD", true)]
+ [DataRow("EntraID", true)]
public void TestBaseRouteIsConfigurableForSWA(string authProvider, bool isExceptionExpected)
{
string[] initArgs = { "init", "-c", TEST_RUNTIME_CONFIG_FILE, "--host-mode", "development", "--database-type", "mssql",
diff --git a/src/Cli.Tests/InitTests.cs b/src/Cli.Tests/InitTests.cs
index a7f6116adf..80eda44788 100644
--- a/src/Cli.Tests/InitTests.cs
+++ b/src/Cli.Tests/InitTests.cs
@@ -302,6 +302,7 @@ public void EnsureFailureOnReInitializingExistingConfig()
[DataRow("AppService", null, null, DisplayName = "AppService with no audience and no issuer specified.")]
[DataRow("Simulator", null, null, DisplayName = "Simulator with no audience and no issuer specified.")]
[DataRow("AzureAD", "aud-xxx", "issuer-xxx", DisplayName = "AzureAD with both audience and issuer specified.")]
+ [DataRow("EntraID", "aud-xxx", "issuer-xxx", DisplayName = "EntraID with both audience and issuer specified.")]
public Task EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders(
string authenticationProvider,
string? audience,
@@ -426,7 +427,7 @@ public Task GraphQLPathWithoutStartingSlashWillHaveItAdded()
///
/// b. When --graphql.multiple-create.enabled option is not used
/// - In this case, fields related to multiple mutation and multiple create operations will NOT be written to the config file.
- ///
+ ///
///
[DataTestMethod]
[DataRow(DatabaseType.MSSQL, CliBool.True, DisplayName = "Init command with '--graphql.multiple-create.enabled true' for MsSQL database type")]
@@ -453,7 +454,7 @@ public Task VerifyCorrectConfigGenerationWithMultipleMutationOptions(DatabaseTyp
if (databaseType is DatabaseType.CosmosDB_NoSQL)
{
- // A schema file is added since its mandatory for CosmosDB_NoSQL
+ // A schema file is added since its mandatory for CosmosDB_NoSQL
((MockFileSystem)_fileSystem!).AddFile(TEST_SCHEMA_FILE, new MockFileData(""));
options = new(
diff --git a/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_daacbd948b7ef72f.verified.txt b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_daacbd948b7ef72f.verified.txt
new file mode 100644
index 0000000000..7a67eca701
--- /dev/null
+++ b/src/Cli.Tests/Snapshots/InitTests.EnsureCorrectConfigGenerationWithDifferentAuthenticationProviders_daacbd948b7ef72f.verified.txt
@@ -0,0 +1,34 @@
+{
+ DataSource: {
+ DatabaseType: MSSQL,
+ Options: {
+ set-session-context: false
+ }
+ },
+ Runtime: {
+ Rest: {
+ Enabled: true,
+ Path: /api,
+ RequestBodyStrict: true
+ },
+ GraphQL: {
+ Enabled: true,
+ Path: /graphql,
+ AllowIntrospection: true
+ },
+ Host: {
+ Cors: {
+ AllowCredentials: false
+ },
+ Authentication: {
+ Provider: EntraID,
+ Jwt: {
+ Audience: aud-xxx,
+ Issuer: issuer-xxx
+ }
+ },
+ Mode: Production
+ }
+ },
+ Entities: []
+}
\ No newline at end of file
diff --git a/src/Cli.Tests/TestHelper.cs b/src/Cli.Tests/TestHelper.cs
index 6e06abf61b..a5a0079797 100644
--- a/src/Cli.Tests/TestHelper.cs
+++ b/src/Cli.Tests/TestHelper.cs
@@ -175,6 +175,32 @@ public static Process ExecuteDabCommand(string command, string flags)
}
}";
+ ///
+ /// Only Runtime section containing both rest and graphql enabled. The authentication provider can be replaced with <>.
+ ///
+ public const string RUNTIME_SECTION_JWT_AUTHENTICATION_PLACEHOLDER = @"
+ ""runtime"": {
+ ""rest"": {
+ ""path"": ""/api"",
+ ""enabled"": true
+ },
+ ""graphql"": {
+ ""path"": ""/graphql"",
+ ""enabled"": true,
+ ""allow-introspection"": true
+ },
+ ""host"": {
+ ""mode"": ""development"",
+ ""cors"": {
+ ""origins"": [],
+ ""allow-credentials"": false
+ },
+ ""authentication"": {
+ ""provider"": ""<>""
+ }
+ }
+ }";
+
///
/// Configuration with unresolved environment variable references on
/// properties of various data types (string, enum, bool, int).
diff --git a/src/Cli.Tests/UtilsTests.cs b/src/Cli.Tests/UtilsTests.cs
index 84c36a54f7..486d09f253 100644
--- a/src/Cli.Tests/UtilsTests.cs
+++ b/src/Cli.Tests/UtilsTests.cs
@@ -207,6 +207,10 @@ public void TestApiPathIsWellFormed(string apiPath, bool expectSuccess)
[DataRow("AzureAD", null, "issuer-xxx", false, DisplayName = "FAIL: AzureAD incorrectly configured with no audience specified.")]
[DataRow("AzureAD", "aud-xxx", null, false, DisplayName = "FAIL: AzureAD incorrectly configured with no issuer specified.")]
[DataRow("AzureAD", null, null, false, DisplayName = "FAIL: AzureAD incorrectly configured with no audience or issuer specified.")]
+ [DataRow("EntraID", "aud-xxx", "issuer-xxx", true, DisplayName = "PASS: EntraID correctly configured with both audience and issuer.")]
+ [DataRow("EntraID", null, "issuer-xxx", false, DisplayName = "FAIL: EntraID incorrectly configured with no audience specified.")]
+ [DataRow("EntraID", "aud-xxx", null, false, DisplayName = "FAIL: EntraID incorrectly configured with no issuer specified.")]
+ [DataRow("EntraID", null, null, false, DisplayName = "FAIL: EntraID incorrectly configured with no audience or issuer specified.")]
public void TestValidateAudienceAndIssuerForAuthenticationProvider(
string authenticationProvider,
string? audience,
diff --git a/src/Cli.Tests/ValidateConfigTests.cs b/src/Cli.Tests/ValidateConfigTests.cs
index 9560d5ce7e..318819f8be 100644
--- a/src/Cli.Tests/ValidateConfigTests.cs
+++ b/src/Cli.Tests/ValidateConfigTests.cs
@@ -145,6 +145,32 @@ public void TestValidateConfigFailsWithInvalidGraphQLDepthLimit(object? depthLim
}
}
+ ///
+ /// This Test is used to verify that DAB fails when the JWT properties are missing for OAuth based providers
+ ///
+ [DataTestMethod]
+ [DataRow("AzureAD")]
+ [DataRow("EntraID")]
+ public void TestMissingJwtProperties(string authScheme)
+ {
+ string ConfigWithJwtAuthentication = $"{{{SAMPLE_SCHEMA_DATA_SOURCE}, {RUNTIME_SECTION_JWT_AUTHENTICATION_PLACEHOLDER}, \"entities\": {{ }}}}";
+ ConfigWithJwtAuthentication = ConfigWithJwtAuthentication.Replace("<>", authScheme, StringComparison.OrdinalIgnoreCase);
+
+ // create an empty config file
+ ((MockFileSystem)_fileSystem!).AddFile(TEST_RUNTIME_CONFIG_FILE, ConfigWithJwtAuthentication);
+
+ ValidateOptions validateOptions = new(TEST_RUNTIME_CONFIG_FILE);
+
+ try
+ {
+ Assert.IsFalse(ConfigGenerator.IsConfigValid(validateOptions, _runtimeConfigLoader!, _fileSystem!));
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail($"Unexpected Exception thrown: {ex.Message}");
+ }
+ }
+
///
/// This Test is used to verify that the validate command is able to catch when data source field or entities field is missing.
///
diff --git a/src/Core/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs b/src/Core/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs
index fa0af6d645..16ca00fb72 100644
--- a/src/Core/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs
+++ b/src/Core/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs
@@ -92,7 +92,7 @@ protected override Task HandleAuthenticateAsync()
if (claimsPrincipal is not null)
{
// AuthenticationTicket is Asp.Net Core Abstraction of Authentication information
- // Ref: aspnetcore/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs
+ // Ref: aspnetcore/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs
AuthenticationTicket ticket = new(claimsPrincipal, EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME);
AuthenticateResult success = AuthenticateResult.Success(ticket);
return Task.FromResult(success);
diff --git a/src/Core/AuthenticationHelpers/SupportedAuthNProviders.cs b/src/Core/AuthenticationHelpers/SupportedAuthNProviders.cs
index e10a1e3977..70a6809074 100644
--- a/src/Core/AuthenticationHelpers/SupportedAuthNProviders.cs
+++ b/src/Core/AuthenticationHelpers/SupportedAuthNProviders.cs
@@ -6,9 +6,12 @@ namespace Azure.DataApiBuilder.Core.AuthenticationHelpers;
internal static class SupportedAuthNProviders
{
public const string APP_SERVICE = "AppService";
+
public const string AZURE_AD = "AzureAD";
public const string ENTRA_ID = "EntraID";
+
public const string GENERIC_OAUTH = "Custom";
public const string SIMULATOR = "Simulator";
+
public const string STATIC_WEB_APPS = "StaticWebApps";
}
diff --git a/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs b/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs
index 15db8198b8..8b01d29961 100644
--- a/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs
+++ b/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs
@@ -59,14 +59,16 @@ public void ValidateEasyAuthConfig()
}
}
- [TestMethod("AuthN validation passes when all values are provided when provider not EasyAuth")]
- public void ValidateJwtConfigParamsSet()
+ [DataTestMethod("AuthN validation passes when all values are provided when provider not EasyAuth")]
+ [DataRow("AzureAD")]
+ [DataRow("EntraID")]
+ public void ValidateJwtConfigParamsSet(string authenticationProvider)
{
JwtOptions jwt = new(
Audience: "12345",
Issuer: "https://login.microsoftonline.com/common");
AuthenticationOptions authNConfig = new(
- Provider: "AzureAD",
+ Provider: authenticationProvider,
Jwt: jwt);
RuntimeConfig config = CreateRuntimeConfigWithOptionalAuthN(authNConfig);
@@ -108,14 +110,16 @@ public void ValidateAuthNSectionNotNecessary()
}
}
- [TestMethod("AuthN validation fails when either Issuer or Audience not provided not EasyAuth")]
- public void ValidateFailureWithIncompleteJwtConfig()
+ [DataTestMethod("AuthN validation fails when either Issuer or Audience not provided not EasyAuth")]
+ [DataRow("AzureAD")]
+ [DataRow("EntraID")]
+ public void ValidateFailureWithIncompleteJwtConfig(string authenticationProvider)
{
JwtOptions jwt = new(
Audience: "12345",
Issuer: string.Empty);
AuthenticationOptions authNConfig = new(
- Provider: "AzureAD",
+ Provider: authenticationProvider,
Jwt: jwt);
RuntimeConfig config = CreateRuntimeConfigWithOptionalAuthN(authNConfig);
@@ -136,7 +140,7 @@ public void ValidateFailureWithIncompleteJwtConfig()
Audience: string.Empty,
Issuer: DEFAULT_ISSUER);
authNConfig = new(
- Provider: "AzureAD",
+ Provider: authenticationProvider,
Jwt: jwt);
config = CreateRuntimeConfigWithOptionalAuthN(authNConfig);
diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs
index 2ad87c71f2..c94d5a9a80 100644
--- a/src/Service.Tests/Configuration/ConfigurationTests.cs
+++ b/src/Service.Tests/Configuration/ConfigurationTests.cs
@@ -1402,7 +1402,7 @@ public async Task TestValidateConfigForValidDepthLimit(int? depthLimit)
///
/// This method validates that depth-limit outside the valid range should fail validation
- /// during `dab validate` and `dab start`.
+ /// during `dab validate` and `dab start`.
///
///
///
@@ -4396,7 +4396,7 @@ public async Task ValidateNextLinkUsage()
///
/// Tests the enforcement of depth limit restrictions on GraphQL queries and mutations in non-hosted mode.
- /// Verifies that requests exceeding the specified depth limit result in a BadRequest,
+ /// Verifies that requests exceeding the specified depth limit result in a BadRequest,
/// while requests within the limit succeed with the expected status code.
/// Also verifies that the error message contains the current and allowed max depth limit value.
/// Example: