Skip to content

Commit

Permalink
Implementation of #1603 - WithInstanceMetadata from custom uri
Browse files Browse the repository at this point in the history
  • Loading branch information
bgavrilMS committed Mar 12, 2020
1 parent 03557ea commit 75ef92a
Show file tree
Hide file tree
Showing 14 changed files with 598 additions and 347 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ public T WithInstanceDicoveryMetadata(string instanceDiscoveryJson)
}
}

/// <summary>
/// Allows developers to configure their own valid authorities. A Uri that returns a response similar to https://aka.ms/aad-instance-discovery
/// should be provided. MSAL uses this information to:
/// <list type="bullet">
/// <item>Call REST APIs on the environment specified in the preferred_network</item>
/// <item>Identify an environment under which to save tokens and accounts in the cache</item>
/// <item>Use the environment aliases to match tokens issued to other authorities</item>
/// </list>
/// For more details see https://aka.ms/msal-net-custom-instance-metadata
/// </summary>
/// <remarks>
/// Developers take responsibility for authority validation if they use this method. Should not be used when the authority is not know in advance.
/// Has no effect on ADFS or B2C authorities, only for AAD authorities</remarks>
/// <param name="instanceDiscoveryUri"></param>
/// <returns></returns>
public T WithInstanceDicoveryMetadata(Uri instanceDiscoveryUri)
{
Config.CustomInstanceDiscoveryMetadataUri = instanceDiscoveryUri ??
throw new ArgumentNullException(nameof(instanceDiscoveryUri));

return (T)this;
}

internal T WithHttpManager(IHttpManager httpManager)
{
Config.HttpManager = httpManager;
Expand Down Expand Up @@ -358,7 +381,15 @@ internal virtual void Validate()
throw new MsalClientException(MsalError.ClientIdMustBeAGuid, MsalErrorMessage.ClientIdMustBeAGuid);
}

if (Config.AuthorityInfo.ValidateAuthority && Config.CustomInstanceDiscoveryMetadata != null)
if (Config.CustomInstanceDiscoveryMetadata != null && Config.CustomInstanceDiscoveryMetadataUri != null)
{
throw new MsalClientException(
MsalError.CustomMetadataInstanceOrUri,
MsalErrorMessage.CustomMetadataInstanceOrUri);
}

if (Config.AuthorityInfo.ValidateAuthority &&
(Config.CustomInstanceDiscoveryMetadata != null || Config.CustomInstanceDiscoveryMetadataUri != null))
{
throw new MsalClientException(MsalError.ValidateAuthorityOrCustomMetadata, MsalErrorMessage.ValidateAuthorityOrCustomMetadata);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public string ClientVersion
#region Authority

public InstanceDiscoveryResponse CustomInstanceDiscoveryMetadata { get; set; }
public Uri CustomInstanceDiscoveryMetadataUri { get; set; }

/// <summary>
/// Should _not_ go in the interface, only for builder usage while determining authorities with ApplicationOptions
Expand Down
8 changes: 7 additions & 1 deletion src/client/Microsoft.Identity.Client/Core/ServiceBundle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ internal class ServiceBundle : IServiceBundle
TelemetryManager = new TelemetryManager(config, PlatformProxy, config.TelemetryCallback);
}

InstanceDiscoveryManager = new InstanceDiscoveryManager(HttpManager, TelemetryManager, shouldClearCaches, config.CustomInstanceDiscoveryMetadata);
InstanceDiscoveryManager = new InstanceDiscoveryManager(
HttpManager,
TelemetryManager,
shouldClearCaches,
config.CustomInstanceDiscoveryMetadata,
config.CustomInstanceDiscoveryMetadataUri);

WsTrustWebRequestManager = new WsTrustWebRequestManager(HttpManager);
AuthorityEndpointResolutionManager = new AuthorityEndpointResolutionManager(this, shouldClearCaches);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ internal class InstanceDiscoveryManager : IInstanceDiscoveryManager
IHttpManager httpManager,
ITelemetryManager telemetryManager,
bool /* for test */ shouldClearCaches,
InstanceDiscoveryResponse userProviderInstanceDiscoveryResponse) :
InstanceDiscoveryResponse userProviderInstanceDiscoveryResponse = null,
Uri userProvidedInstanceDiscoveryUri = null) :
this(
httpManager,
telemetryManager,
shouldClearCaches,
userProviderInstanceDiscoveryResponse != null ? new UserMetadataProvider(userProviderInstanceDiscoveryResponse) : null,
userProvidedInstanceDiscoveryUri,
null, null, null)
{
}
Expand All @@ -54,6 +56,7 @@ internal class InstanceDiscoveryManager : IInstanceDiscoveryManager
ITelemetryManager telemetryManager,
bool shouldClearCaches,
IUserMetadataProvider userMetadataProvider = null,
Uri userProvidedInstanceDiscoveryUri = null,
IKnownMetadataProvider knownMetadataProvider = null,
INetworkCacheMetadataProvider networkCacheMetadataProvider = null,
INetworkMetadataProvider networkMetadataProvider = null)
Expand All @@ -65,7 +68,11 @@ internal class InstanceDiscoveryManager : IInstanceDiscoveryManager
_knownMetadataProvider = knownMetadataProvider ?? new KnownMetadataProvider();
_networkCacheMetadataProvider = networkCacheMetadataProvider ?? new NetworkCacheMetadataProvider();
_networkMetadataProvider = networkMetadataProvider ??
new NetworkMetadataProvider(_httpManager, _telemetryManager, _networkCacheMetadataProvider);
new NetworkMetadataProvider(
_httpManager,
_telemetryManager,
_networkCacheMetadataProvider,
userProvidedInstanceDiscoveryUri);

if (shouldClearCaches)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ internal class NetworkMetadataProvider : INetworkMetadataProvider
private readonly IHttpManager _httpManager;
private readonly ITelemetryManager _telemetryManager;
private readonly INetworkCacheMetadataProvider _networkCacheMetadataProvider;
private readonly Uri _userProvidedInstanceDiscoveryUri; // can be null

public NetworkMetadataProvider(
IHttpManager httpManager,
ITelemetryManager telemetryManager,
INetworkCacheMetadataProvider networkCacheMetadataProvider)
INetworkCacheMetadataProvider networkCacheMetadataProvider,
Uri userProvidedInstanceDiscoveryUri = null)
{
_httpManager = httpManager;
_telemetryManager = telemetryManager;
_networkCacheMetadataProvider = networkCacheMetadataProvider;
_httpManager = httpManager ?? throw new ArgumentNullException(nameof(httpManager));
_telemetryManager = telemetryManager ?? throw new ArgumentNullException(nameof(telemetryManager));
_networkCacheMetadataProvider = networkCacheMetadataProvider ?? throw new ArgumentNullException(nameof(networkCacheMetadataProvider));
_userProvidedInstanceDiscoveryUri = userProvidedInstanceDiscoveryUri; // can be null
}

public async Task<InstanceDiscoveryMetadataEntry> GetMetadataAsync(Uri authority, RequestContext requestContext)
Expand Down Expand Up @@ -78,20 +81,38 @@ private void CacheInstanceDiscoveryMetadata(InstanceDiscoveryResponse instanceDi
client.AddQueryParameter("api-version", "1.1");
client.AddQueryParameter("authorization_endpoint", BuildAuthorizeEndpoint(authority));

Uri instanceDiscoveryEndpoint = ComputeHttpEndpoint(authority, requestContext);

InstanceDiscoveryResponse discoveryResponse = await client
.DiscoverAadInstanceAsync(instanceDiscoveryEndpoint, requestContext)
.ConfigureAwait(false);

return discoveryResponse;
}

private Uri ComputeHttpEndpoint(Uri authority, RequestContext requestContext)
{
if (_userProvidedInstanceDiscoveryUri != null)
{
return _userProvidedInstanceDiscoveryUri;
}

string discoveryHost = KnownMetadataProvider.IsKnownEnvironment(authority.Host) ?
authority.Host :
AadAuthority.DefaultTrustedHost;
string instanceDiscoveryEndpoint = BuildInstanceDiscoveryEndpoint(discoveryHost, authority.Port);

string instanceDiscoveryEndpoint = UriBuilderExtensions.GetHttpsUriWithOptionalPort(
string.Format(
CultureInfo.InvariantCulture,
"https://{0}/common/discovery/instance",
discoveryHost),
authority.Port);

requestContext.Logger.InfoPii(
$"Fetching instance discovery from the network from host {discoveryHost}. Endpoint {instanceDiscoveryEndpoint}",
$"Fetching instance discovery from the network from host {discoveryHost}");

InstanceDiscoveryResponse discoveryResponse = await client
.DiscoverAadInstanceAsync(new Uri(instanceDiscoveryEndpoint), requestContext)
.ConfigureAwait(false);

return discoveryResponse;
return new Uri(instanceDiscoveryEndpoint);
}

private static string BuildAuthorizeEndpoint(Uri authority)
Expand All @@ -105,9 +126,5 @@ private static string GetTenant(Uri uri)
return uri.AbsolutePath.Split('/')[1];
}

private static string BuildInstanceDiscoveryEndpoint(string host, int port)
{
return UriBuilderExtensions.GetHttpsUriWithOptionalPort(string.Format(CultureInfo.InvariantCulture, "https://{0}/common/discovery/instance", host), port);
}
}
}
9 changes: 8 additions & 1 deletion src/client/Microsoft.Identity.Client/MsalError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ public static class MsalError
public const string InvalidUserInstanceMetadata = "invalid-custom-instance-metadata";

/// <summary>
/// <para>What happens?</para>You have configured your own instance metadata, and have also configured custom metadata. These are mutually exclusive.
/// <para>What happens?</para>You have configured your own instance metadata, and have also set validate authority to true. These are mutually exclusive.
/// <para>Mitigation</para>Set the validate authority flag to false. See https://aka.ms/msal-net-custom-instance-metadata for more details.
/// </summary>
public const string ValidateAuthorityOrCustomMetadata = "validate_authority_or_custom_instance_metadata";
Expand Down Expand Up @@ -768,6 +768,13 @@ public static class MsalError
/// </summary>
public const string AuthorityTypeMismatch = "authority_type_mismatch";


/// <summary>
/// <para>What happens?</para>You have configured your own instance metadata using both an Uri and a string. Only one is supported.
/// <para>Mitigation</para>Call WithInstanceDicoveryMetadata only once. See https://aka.ms/msal-net-custom-instance-metadata for more details.
/// </summary>
public const string CustomMetadataInstanceOrUri = "custom_metadata_instance_or_uri";

#if iOS
/// <summary>
/// Xamarin.iOS specific. This error indicates that keychain access has not be enabled for the application.
Expand Down
3 changes: 3 additions & 0 deletions src/client/Microsoft.Identity.Client/MsalErrorMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ public static string RedirectUriMismatch(string expectedUri, string actualUri)

public const string NoAndroidBrokerAccountFound = "Android account manager could not find an account that matched the provided account information.";
public const string AndroidBrokerCannotBeInvoked = "In order to perform brokered authentication on android you need to ensure that you have installed either Intune Company Portal (5.0.4689.0 or greater) or Microsoft Authenticator (6.2001.0140 or greater).";
public const string CustomMetadataInstanceOrUri = "You have configured your own instance metadata using both an Uri and a string. Only one is supported. " +
"See https://aka.ms/msal-net-custom-instance-metadata for more details.";

public static string ExperimentalFeature(string methodName)
{
return string.Format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,35 @@ namespace Microsoft.Identity.Test.Common.Core.Mocks
{
internal static class MockHttpManagerExtensions
{
public static void AddInstanceDiscoveryMockHandler(this MockHttpManager httpManager)
{
AddInstanceDiscoveryMockHandler(httpManager, TestConstants.AuthorityCommonTenant);
}

public static void AddInstanceDiscoveryMockHandler(this MockHttpManager httpManager, string authority)
public static void AddInstanceDiscoveryMockHandler(
this MockHttpManager httpManager,
string authority = TestConstants.AuthorityCommonTenant,
Uri customDiscoveryEndpoint = null,
string instanceMetadataContent = null)
{
Uri authorityURI = new Uri(authority);
string discoveryHost = KnownMetadataProvider.IsKnownEnvironment(authorityURI.Host)
? authorityURI.Host
: AadAuthority.DefaultTrustedHost;

string discoveryEndpoint = UriBuilderExtensions.GetHttpsUriWithOptionalPort($"https://{discoveryHost}/common/discovery/instance", authorityURI.Port);
string discoveryEndpoint;

if (customDiscoveryEndpoint == null)
{
string discoveryHost = KnownMetadataProvider.IsKnownEnvironment(authorityURI.Host)
? authorityURI.Host
: AadAuthority.DefaultTrustedHost;

discoveryEndpoint = UriBuilderExtensions.GetHttpsUriWithOptionalPort($"https://{discoveryHost}/common/discovery/instance", authorityURI.Port);
}
else
{
discoveryEndpoint = customDiscoveryEndpoint.AbsoluteUri;
}

httpManager.AddMockHandler(
MockHelpers.CreateInstanceDiscoveryMockHandler(discoveryEndpoint));
MockHelpers.CreateInstanceDiscoveryMockHandler(
discoveryEndpoint,
instanceMetadataContent ?? TestConstants.DiscoveryJsonResponse));
}


public static void AddResponseMockHandlerForPost(
this MockHttpManager httpManager,
HttpResponseMessage responseMessage,
Expand Down
2 changes: 1 addition & 1 deletion tests/Microsoft.Identity.Test.Common/TestCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static void ResetInternalStaticCaches()
new InstanceDiscoveryManager(
Substitute.For<IHttpManager>(),
Substitute.For<ITelemetryManager>(),
true);
true, null, null);
new AuthorityEndpointResolutionManager(null, true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,29 @@ public void TestConstructor_InstanceMetadata_ValidateAuthority_MutuallyExclusive
Assert.AreEqual(ex.ErrorCode, MsalError.ValidateAuthorityOrCustomMetadata);
}

[TestMethod]
public void TestConstructor_InstanceMetadataUri_ValidateAuthority_MutuallyExclusive()
{
var ex = AssertException.Throws<MsalClientException>(() => PublicClientApplicationBuilder.Create(TestConstants.ClientId)
.WithInstanceDicoveryMetadata(new Uri("https://some_uri.com"))
.WithAuthority("https://some.authority/bogus/", true)
.Build());
Assert.AreEqual(ex.ErrorCode, MsalError.ValidateAuthorityOrCustomMetadata);
}

[TestMethod]
[DeploymentItem(@"Resources\CustomInstanceMetadata.json")]
public void TestConstructor_WithInstanceDicoveryMetadata_OnlyOneOverload()
{
string instanceMetadataJson = File.ReadAllText(ResourceHelper.GetTestResourceRelativePath("CustomInstanceMetadata.json"));
var ex = AssertException.Throws<MsalClientException>(() => PublicClientApplicationBuilder.Create(TestConstants.ClientId)
.WithInstanceDicoveryMetadata(instanceMetadataJson)
.WithInstanceDicoveryMetadata(new Uri("https://some_uri.com"))
.WithAuthority("https://some.authority/bogus/", true)
.Build());
Assert.AreEqual(ex.ErrorCode, MsalError.CustomMetadataInstanceOrUri);
}

[TestMethod]
public void TestConstructor_BadInstanceMetadata()
{
Expand Down
Loading

0 comments on commit 75ef92a

Please sign in to comment.