diff --git a/src/Microsoft.Health.Fhir.Api/Configs/FhirServerConfiguration.cs b/src/Microsoft.Health.Fhir.Api/Configs/FhirServerConfiguration.cs
index 8b494397cf..09a65e45e4 100644
--- a/src/Microsoft.Health.Fhir.Api/Configs/FhirServerConfiguration.cs
+++ b/src/Microsoft.Health.Fhir.Api/Configs/FhirServerConfiguration.cs
@@ -38,5 +38,7 @@ public class FhirServerConfiguration : IApiConfiguration
public EncryptionConfiguration Encryption { get; } = new EncryptionConfiguration();
public ResourceManagerConfig ResourceManager { get; } = new ResourceManagerConfig();
+
+ public SmartIdentityProviderConfiguration SmartIdentityProvider { get; } = new SmartIdentityProviderConfiguration();
}
}
diff --git a/src/Microsoft.Health.Fhir.Core/Configs/SecurityConfiguration.cs b/src/Microsoft.Health.Fhir.Core/Configs/SecurityConfiguration.cs
index fcd0b5bda5..ad0aca8603 100644
--- a/src/Microsoft.Health.Fhir.Core/Configs/SecurityConfiguration.cs
+++ b/src/Microsoft.Health.Fhir.Core/Configs/SecurityConfiguration.cs
@@ -26,11 +26,5 @@ public class SecurityConfiguration
public string ServicePrincipalClientId { get; set; }
public AddAuthenticationLibraryMethod AddAuthenticationLibrary { get; set; }
-
- public string IntrospectionEndpoint { get; set; }
-
- public string ManagementEndpoint { get; set; }
-
- public string RevocationEndpoint { get; set; }
}
}
diff --git a/src/Microsoft.Health.Fhir.Core/Configs/SmartIdentityProviderConfiguration.cs b/src/Microsoft.Health.Fhir.Core/Configs/SmartIdentityProviderConfiguration.cs
new file mode 100644
index 0000000000..e5f4ad2d5e
--- /dev/null
+++ b/src/Microsoft.Health.Fhir.Core/Configs/SmartIdentityProviderConfiguration.cs
@@ -0,0 +1,34 @@
+// -------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+// -------------------------------------------------------------------------------------------------
+
+namespace Microsoft.Health.Fhir.Core.Configs
+{
+ ///
+ /// Configuration for a third-party SMART identity provider.
+ ///
+ public class SmartIdentityProviderConfiguration
+ {
+ ///
+ /// Gets or sets the authority URL for the third-party identity provider.
+ /// This overrides the default authority from SecurityConfiguration when specified.
+ ///
+ public string Authority { get; set; }
+
+ ///
+ /// Gets or sets the introspection endpoint URL for token introspection.
+ ///
+ public string Introspection { get; set; }
+
+ ///
+ /// Gets or sets the management endpoint URL for application management.
+ ///
+ public string Management { get; set; }
+
+ ///
+ /// Gets or sets the revocation endpoint URL for token revocation.
+ ///
+ public string Revocation { get; set; }
+ }
+}
diff --git a/src/Microsoft.Health.Fhir.Core/Features/Conformance/GetSmartConfigurationHandler.cs b/src/Microsoft.Health.Fhir.Core/Features/Conformance/GetSmartConfigurationHandler.cs
index 5bf0c544c5..6e63444270 100644
--- a/src/Microsoft.Health.Fhir.Core/Features/Conformance/GetSmartConfigurationHandler.cs
+++ b/src/Microsoft.Health.Fhir.Core/Features/Conformance/GetSmartConfigurationHandler.cs
@@ -22,12 +22,17 @@ namespace Microsoft.Health.Fhir.Core.Features.Conformance
public class GetSmartConfigurationHandler : IRequestHandler
{
private readonly SecurityConfiguration _securityConfiguration;
+ private readonly SmartIdentityProviderConfiguration _smartIdentityProviderConfiguration;
- public GetSmartConfigurationHandler(IOptions securityConfigurationOptions)
+ public GetSmartConfigurationHandler(
+ IOptions securityConfigurationOptions,
+ IOptions smartIdentityProviderConfiguration)
{
EnsureArg.IsNotNull(securityConfigurationOptions?.Value, nameof(securityConfigurationOptions));
+ EnsureArg.IsNotNull(smartIdentityProviderConfiguration?.Value, nameof(smartIdentityProviderConfiguration));
_securityConfiguration = securityConfigurationOptions.Value;
+ _smartIdentityProviderConfiguration = smartIdentityProviderConfiguration.Value;
}
public Task Handle(GetSmartConfigurationRequest request, CancellationToken cancellationToken)
@@ -43,7 +48,7 @@ protected GetSmartConfigurationResponse Handle(GetSmartConfigurationRequest requ
{
try
{
- string baseEndpoint = _securityConfiguration.Authentication.Authority;
+ string baseEndpoint = GetAuthority();
Uri authorizationEndpoint = new Uri(baseEndpoint + "/authorize");
Uri tokenEndpoint = new Uri(baseEndpoint + "/token");
@@ -105,9 +110,9 @@ protected GetSmartConfigurationResponse Handle(GetSmartConfigurationRequest requ
grantTypesSupported,
tokenEndpointAuthMethodsSupported,
responseTypesSupported,
- _securityConfiguration.IntrospectionEndpoint,
- _securityConfiguration.ManagementEndpoint,
- _securityConfiguration.RevocationEndpoint);
+ _smartIdentityProviderConfiguration.Introspection,
+ _smartIdentityProviderConfiguration.Management,
+ _smartIdentityProviderConfiguration.Revocation);
}
catch (Exception e) when (e is ArgumentNullException || e is UriFormatException)
{
@@ -121,5 +126,12 @@ protected GetSmartConfigurationResponse Handle(GetSmartConfigurationRequest requ
Core.Resources.SecurityConfigurationAuthorizationNotEnabled,
HttpStatusCode.BadRequest);
}
+
+ private string GetAuthority()
+ {
+ var authority = !string.IsNullOrEmpty(_smartIdentityProviderConfiguration.Authority) ?
+ _smartIdentityProviderConfiguration.Authority : _securityConfiguration.Authentication.Authority;
+ return authority?.TrimEnd('/');
+ }
}
}
diff --git a/src/Microsoft.Health.Fhir.Shared.Api/Registration/FhirServerServiceCollectionExtensions.cs b/src/Microsoft.Health.Fhir.Shared.Api/Registration/FhirServerServiceCollectionExtensions.cs
index 1cf0623aa3..4c50cb3ca7 100644
--- a/src/Microsoft.Health.Fhir.Shared.Api/Registration/FhirServerServiceCollectionExtensions.cs
+++ b/src/Microsoft.Health.Fhir.Shared.Api/Registration/FhirServerServiceCollectionExtensions.cs
@@ -93,6 +93,7 @@ public static IFhirServerBuilder AddFhirServer(
services.AddSingleton(Options.Options.Create(fhirServerConfiguration.Operations.Terminology));
services.AddSingleton(Options.Options.Create(fhirServerConfiguration.Audit));
services.AddSingleton(Options.Options.Create(fhirServerConfiguration.Bundle));
+ services.AddSingleton(Options.Options.Create(fhirServerConfiguration.SmartIdentityProvider));
services.AddSingleton();
services.AddSingleton(provider =>
{
diff --git a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Conformance/GetSmartConfigurationHandlerTests.cs b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Conformance/GetSmartConfigurationHandlerTests.cs
index 2b25b8eefb..d3a62a6eee 100644
--- a/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Conformance/GetSmartConfigurationHandlerTests.cs
+++ b/src/Microsoft.Health.Fhir.Shared.Core.UnitTests/Features/Conformance/GetSmartConfigurationHandlerTests.cs
@@ -36,7 +36,7 @@ public async Task GivenASmartConfigurationHandler_WhenSecurityConfigurationNotEn
var securityConfiguration = new SecurityConfiguration();
securityConfiguration.Authorization.Enabled = false;
- var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration));
+ var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration), Options.Create(new SmartIdentityProviderConfiguration()));
OperationFailedException e = await Assert.ThrowsAsync(() => handler.Handle(request, CancellationToken.None));
Assert.Equal(HttpStatusCode.BadRequest, e.ResponseStatusCode);
@@ -53,7 +53,7 @@ public async Task GivenASmartConfigurationHandler_WhenSecurityConfigurationEnabl
securityConfiguration.Authorization.Enabled = true;
securityConfiguration.Authentication.Authority = baseEndpoint;
- var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration));
+ var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration), Options.Create(new SmartIdentityProviderConfiguration()));
GetSmartConfigurationResponse response = await handler.Handle(request, CancellationToken.None);
@@ -86,7 +86,7 @@ public async Task GivenASmartConfigurationHandler_WhenBaseEndpointIsInvalid_Then
securityConfiguration.Authorization.Enabled = true;
securityConfiguration.Authentication.Authority = baseEndpoint;
- var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration));
+ var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration), Options.Create(new SmartIdentityProviderConfiguration()));
OperationFailedException exception = await Assert.ThrowsAsync(() => handler.Handle(request, CancellationToken.None));
Assert.Equal(HttpStatusCode.BadRequest, exception.ResponseStatusCode);
@@ -97,7 +97,7 @@ public async Task GivenASmartConfigurationHandler_WhenBaseEndpointIsInvalid_Then
[InlineData(null, "https://ehr.example.com/user/manage", null)]
[InlineData(null, null, "https://ehr.example.com/user/revoke")]
[InlineData("https://ehr.example.com/user/introspect", "https://ehr.example.com/user/manage", "https://ehr.example.com/user/revoke")]
- public async Task GivenASmartConfigurationHandler_WhenOtherEndpointsAreSpecifired_ThenSmartConfigurationShouldContainsOtherEndpoints(
+ public async Task GivenASmartConfigurationHandler_WhenOtherEndpointsAreSpecified_ThenSmartConfigurationShouldContainOtherEndpoints(
string introspectionEndpoint,
string managementEndpoint,
string revocationEndpoint)
@@ -109,11 +109,13 @@ public async Task GivenASmartConfigurationHandler_WhenOtherEndpointsAreSpecifire
var securityConfiguration = new SecurityConfiguration();
securityConfiguration.Authorization.Enabled = true;
securityConfiguration.Authentication.Authority = baseEndpoint;
- securityConfiguration.IntrospectionEndpoint = introspectionEndpoint;
- securityConfiguration.ManagementEndpoint = managementEndpoint;
- securityConfiguration.RevocationEndpoint = revocationEndpoint;
- var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration));
+ var smartIdentityProviderConfiguration = new SmartIdentityProviderConfiguration();
+ smartIdentityProviderConfiguration.Introspection = introspectionEndpoint;
+ smartIdentityProviderConfiguration.Management = managementEndpoint;
+ smartIdentityProviderConfiguration.Revocation = revocationEndpoint;
+
+ var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration), Options.Create(smartIdentityProviderConfiguration));
GetSmartConfigurationResponse response = await handler.Handle(request, CancellationToken.None);
@@ -138,7 +140,7 @@ public async Task GivenASmartConfigurationHandler_WhenAadSmartOnFhirProxyEnabled
securityConfiguration.Authentication.Authority = baseEndpoint;
securityConfiguration.EnableAadSmartOnFhirProxy = true;
- var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration));
+ var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration), Options.Create(new SmartIdentityProviderConfiguration()));
GetSmartConfigurationResponse response = await handler.Handle(request, CancellationToken.None);
@@ -171,7 +173,7 @@ public async Task GivenASmartConfigurationHandler_WhenAadSmartOnFhirProxyDisable
securityConfiguration.Authentication.Authority = "https://logon.onmicrosoft.com/guid";
securityConfiguration.EnableAadSmartOnFhirProxy = false;
- var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration));
+ var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration), Options.Create(new SmartIdentityProviderConfiguration()));
GetSmartConfigurationResponse response = await handler.Handle(request, CancellationToken.None);
@@ -192,5 +194,30 @@ public async Task GivenASmartConfigurationHandler_WhenAadSmartOnFhirProxyDisable
Assert.NotNull(response.TokenEndpointAuthMethodsSupported);
Assert.NotNull(response.ResponseTypesSupported);
}
+
+ [Theory]
+ [InlineData("https://smart.example.com/")]
+ [InlineData(null)]
+ public async Task GivenASmartConfigurationHandler_When3rdPartyIdpSpecified_ThenCorrectAuthorityEndpointShouldBeReturned(string authority)
+ {
+ var requestUri = new System.Uri("https://fhir.example.com/");
+ var request = new GetSmartConfigurationRequest(requestUri);
+
+ var baseUri = "https://logon.onmicrosoft.com/guid";
+ var securityConfiguration = new SecurityConfiguration();
+ securityConfiguration.Authorization.Enabled = true;
+ securityConfiguration.Authentication.Authority = baseUri;
+
+ var smartIdentityProviderConfiguration = new SmartIdentityProviderConfiguration();
+ smartIdentityProviderConfiguration.Authority = authority;
+
+ var handler = new GetSmartConfigurationHandler(Options.Create(securityConfiguration), Options.Create(smartIdentityProviderConfiguration));
+
+ GetSmartConfigurationResponse response = await handler.Handle(request, CancellationToken.None);
+
+ var expectedUri = !string.IsNullOrEmpty(authority) ? authority : baseUri;
+ Assert.Equal(expectedUri.TrimEnd('/') + "/authorize", response.AuthorizationEndpoint.ToString());
+ Assert.Equal(expectedUri.TrimEnd('/') + "/token", response.TokenEndpoint.ToString());
+ }
}
}