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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Configuration for a third-party SMART identity provider.
/// </summary>
public class SmartIdentityProviderConfiguration
{
/// <summary>
/// Gets or sets the authority URL for the third-party identity provider.
/// This overrides the default authority from SecurityConfiguration when specified.
/// </summary>
public string Authority { get; set; }

/// <summary>
/// Gets or sets the introspection endpoint URL for token introspection.
/// </summary>
public string Introspection { get; set; }

/// <summary>
/// Gets or sets the management endpoint URL for application management.
/// </summary>
public string Management { get; set; }

/// <summary>
/// Gets or sets the revocation endpoint URL for token revocation.
/// </summary>
public string Revocation { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@
public class GetSmartConfigurationHandler : IRequestHandler<GetSmartConfigurationRequest, GetSmartConfigurationResponse>
{
private readonly SecurityConfiguration _securityConfiguration;
private readonly SmartIdentityProviderConfiguration _smartIdentityProviderConfiguration;

public GetSmartConfigurationHandler(IOptions<SecurityConfiguration> securityConfigurationOptions)
public GetSmartConfigurationHandler(
IOptions<SecurityConfiguration> securityConfigurationOptions,
IOptions<SmartIdentityProviderConfiguration> smartIdentityProviderConfiguration)
{
EnsureArg.IsNotNull(securityConfigurationOptions?.Value, nameof(securityConfigurationOptions));
EnsureArg.IsNotNull(smartIdentityProviderConfiguration?.Value, nameof(smartIdentityProviderConfiguration));

_securityConfiguration = securityConfigurationOptions.Value;

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
securityConfigurationOptions
may be null at this access as suggested by
this
null check.
_smartIdentityProviderConfiguration = smartIdentityProviderConfiguration.Value;
}

public Task<GetSmartConfigurationResponse> Handle(GetSmartConfigurationRequest request, CancellationToken cancellationToken)
Expand All @@ -43,7 +48,7 @@
{
try
{
string baseEndpoint = _securityConfiguration.Authentication.Authority;
string baseEndpoint = GetAuthority();
Uri authorizationEndpoint = new Uri(baseEndpoint + "/authorize");
Uri tokenEndpoint = new Uri(baseEndpoint + "/token");

Expand Down Expand Up @@ -105,9 +110,9 @@
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)
{
Expand All @@ -121,5 +126,12 @@
Core.Resources.SecurityConfigurationAuthorizationNotEnabled,
HttpStatusCode.BadRequest);
}

private string GetAuthority()
{
var authority = !string.IsNullOrEmpty(_smartIdentityProviderConfiguration.Authority) ?
_smartIdentityProviderConfiguration.Authority : _securityConfiguration.Authentication.Authority;
return authority?.TrimEnd('/');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ISearchParameterStatusManager, SearchParameterStatusManager>();
services.AddSingleton(provider =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<OperationFailedException>(() => handler.Handle(request, CancellationToken.None));
Assert.Equal(HttpStatusCode.BadRequest, e.ResponseStatusCode);
Expand All @@ -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);

Expand Down Expand Up @@ -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<OperationFailedException>(() => handler.Handle(request, CancellationToken.None));
Assert.Equal(HttpStatusCode.BadRequest, exception.ResponseStatusCode);
Expand All @@ -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)
Expand All @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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);

Expand All @@ -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());
}
}
}
Loading