Skip to content

Commit c4a4b3b

Browse files
committed
expose instanceDiscovery flag
1 parent cb2eca2 commit c4a4b3b

File tree

4 files changed

+191
-17
lines changed

4 files changed

+191
-17
lines changed

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,22 @@
1818

1919
class AadInstanceDiscoveryProvider {
2020

21-
private final static String DEFAULT_TRUSTED_HOST = "login.microsoftonline.com";
22-
private final static String AUTHORIZE_ENDPOINT_TEMPLATE = "https://{host}/{tenant}/oauth2/v2.0/authorize";
23-
private final static String INSTANCE_DISCOVERY_ENDPOINT_TEMPLATE = "https://{host}:{port}/common/discovery/instance";
24-
private final static String INSTANCE_DISCOVERY_REQUEST_PARAMETERS_TEMPLATE = "?api-version=1.1&authorization_endpoint={authorizeEndpoint}";
25-
private final static String HOST_TEMPLATE_WITH_REGION = "{region}.r.{host}";
26-
private final static String SOVEREIGN_HOST_TEMPLATE_WITH_REGION = "{region}.{host}";
27-
private final static String REGION_NAME = "REGION_NAME";
28-
private final static int PORT_NOT_SET = -1;
21+
private static final String DEFAULT_TRUSTED_HOST = "login.microsoftonline.com";
22+
private static final String AUTHORIZE_ENDPOINT_TEMPLATE = "https://{host}/{tenant}/oauth2/v2.0/authorize";
23+
private static final String INSTANCE_DISCOVERY_ENDPOINT_TEMPLATE = "https://{host}:{port}/common/discovery/instance";
24+
private static final String INSTANCE_DISCOVERY_REQUEST_PARAMETERS_TEMPLATE = "?api-version=1.1&authorization_endpoint={authorizeEndpoint}";
25+
private static final String HOST_TEMPLATE_WITH_REGION = "{region}.r.{host}";
26+
private static final String SOVEREIGN_HOST_TEMPLATE_WITH_REGION = "{region}.{host}";
27+
private static final String REGION_NAME = "REGION_NAME";
28+
private static final int PORT_NOT_SET = -1;
2929
// For information of the current api-version refer: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service#versioning
30-
private final static String DEFAULT_API_VERSION = "2020-06-01";
31-
private final static String IMDS_ENDPOINT = "https://169.254.169.254/metadata/instance/compute/location?" + DEFAULT_API_VERSION + "&format=text";
30+
private static final String DEFAULT_API_VERSION = "2020-06-01";
31+
private static final String IMDS_ENDPOINT = "https://169.254.169.254/metadata/instance/compute/location?" + DEFAULT_API_VERSION + "&format=text";
3232

33-
final static TreeSet<String> TRUSTED_HOSTS_SET = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
34-
final static TreeSet<String> TRUSTED_SOVEREIGN_HOSTS_SET = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
33+
static final TreeSet<String> TRUSTED_HOSTS_SET = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
34+
static final TreeSet<String> TRUSTED_SOVEREIGN_HOSTS_SET = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
3535

36-
private final static Logger log = LoggerFactory.getLogger(HttpHelper.class);
36+
private static final Logger log = LoggerFactory.getLogger(AadInstanceDiscoveryProvider.class);
3737

3838
static ConcurrentHashMap<String, InstanceDiscoveryMetadataEntry> cache = new ConcurrentHashMap<>();
3939

@@ -67,10 +67,9 @@ static InstanceDiscoveryMetadataEntry getMetadataEntry(URL authorityUrl,
6767

6868
//If region autodetection is enabled and a specific region not already set,
6969
// set the application's region to the discovered region so that future requests can skip the IMDS endpoint call
70-
if (msalRequest.application().azureRegion() == null && msalRequest.application().autoDetectRegion()) {
71-
if (detectedRegion != null) {
70+
if (msalRequest.application().azureRegion() == null && msalRequest.application().autoDetectRegion()
71+
&& null != detectedRegion) {
7272
msalRequest.application().azureRegion = detectedRegion;
73-
}
7473
}
7574
cacheRegionInstanceMetadata(authorityUrl.getHost(), msalRequest.application().azureRegion());
7675
serviceBundle.getServerSideTelemetry().getCurrentRequest().regionOutcome(
@@ -80,7 +79,16 @@ static InstanceDiscoveryMetadataEntry getMetadataEntry(URL authorityUrl,
8079
InstanceDiscoveryMetadataEntry result = cache.get(host);
8180

8281
if (result == null) {
83-
doInstanceDiscoveryAndCache(authorityUrl, validateAuthority, msalRequest, serviceBundle);
82+
if(msalRequest.application().instanceDiscovery()){
83+
doInstanceDiscoveryAndCache(authorityUrl, validateAuthority, msalRequest, serviceBundle);
84+
} else {
85+
// instanceDiscovery flag is set to False. Do no perform instanceDiscovery.
86+
cache.putIfAbsent(host, InstanceDiscoveryMetadataEntry.builder().
87+
preferredCache(host).
88+
preferredNetwork(host).
89+
aliases(Collections.singleton(host)).
90+
build());
91+
}
8492
}
8593

8694
return cache.get(host);

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AbstractClientApplicationBase.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ public abstract class AbstractClientApplicationBase implements IClientApplicatio
104104
@Getter
105105
protected String azureRegion;
106106

107+
@Accessors(fluent = true)
108+
@Getter
109+
private boolean instanceDiscovery;
110+
107111
@Override
108112
public CompletableFuture<IAuthenticationResult> acquireToken(AuthorizationCodeParameters parameters) {
109113

@@ -325,6 +329,7 @@ public abstract static class Builder<T extends Builder<T>> {
325329
private String azureRegion;
326330
private Integer connectTimeoutForDefaultHttpClient;
327331
private Integer readTimeoutForDefaultHttpClient;
332+
private boolean instanceDiscovery = true;
328333

329334
/**
330335
* Constructor to create instance of Builder of client application
@@ -643,6 +648,30 @@ public T azureRegion(String val) {
643648
return self();
644649
}
645650

651+
/** Historically, MSAL would connect to a central endpoint located at
652+
``https://login.microsoftonline.com`` to acquire some metadata, especially when using an unfamiliar authority.
653+
This behavior is known as Instance Discovery.
654+
This parameter defaults to true, which enables the Instance Discovery.
655+
If you know some authorities which you allow MSAL to operate with as-is,
656+
without involving any Instance Discovery, the recommended pattern is::
657+
knownAuthorities = frozenset([ # Treat your known authorities as const
658+
"https://contoso.com/adfs", "https://login.azs/foo"])
659+
...
660+
authority = "https://contoso.com/adfs" # Assuming your app will use this
661+
app1 = PublicClientApplication(
662+
"client_id",
663+
authority=authority,
664+
# Conditionally disable Instance Discovery for known authorities
665+
instance_discovery=authority not in known_authorities,
666+
)
667+
If you do not know some authorities beforehand,
668+
yet still want MSAL to accept any authority that you will provide,
669+
you can use a ``False`` to unconditionally disable Instance Discovery. */
670+
public T instanceDiscovery(boolean val) {
671+
instanceDiscovery = val;
672+
return self();
673+
}
674+
646675
abstract AbstractClientApplicationBase build();
647676
}
648677

@@ -671,6 +700,7 @@ public T azureRegion(String val) {
671700
clientCapabilities = builder.clientCapabilities;
672701
autoDetectRegion = builder.autoDetectRegion;
673702
azureRegion = builder.azureRegion;
703+
instanceDiscovery = builder.instanceDiscovery;
674704

675705
if (aadAadInstanceDiscoveryResponse != null) {
676706
AadInstanceDiscoveryProvider.cacheInstanceDiscoveryMetadata(

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InstanceDiscoveryMetadataEntry.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
@Accessors(fluent = true)
1313
@Getter(AccessLevel.PACKAGE)
14+
@Setter
1415
@Builder
1516
@NoArgsConstructor
1617
@AllArgsConstructor

msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
import org.powermock.modules.testng.PowerMockTestCase;
99
import org.testng.Assert;
1010
import org.testng.annotations.BeforeMethod;
11+
import org.testng.annotations.DataProvider;
1112
import org.testng.annotations.Test;
1213

1314
import java.net.URI;
1415
import java.net.URL;
16+
import java.util.Collections;
1517

1618
@PrepareForTest(AadInstanceDiscoveryProvider.class)
1719
public class AadInstanceDiscoveryTest extends PowerMockTestCase {
@@ -186,4 +188,137 @@ public void aadInstanceDiscoveryTest_AutoDetectRegion_NoRegionDetected() throws
186188
Assert.assertTrue(entry.aliases().contains("login.microsoft.com"));
187189
Assert.assertTrue(entry.aliases().contains("sts.windows.net"));
188190
}
191+
192+
@DataProvider(name = "aadClouds")
193+
private static Object[][] getAadClouds(){
194+
return new Object[][] {{"https://login.microsoftonline.com/common"} , // #Known to Microsoft
195+
{"https://private.cloud/foo"}//Private Cloud
196+
};
197+
}
198+
199+
@DataProvider(name = "b2cAdfsClouds")
200+
private static Object[][] getNonAadClouds(){
201+
return new Object[][] {{"https://contoso.com/adfs"}//ADFS
202+
// {"https://login.b2clogin.com/contoso/b2c_policy"},//B2C
203+
};
204+
}
205+
206+
/**
207+
* when instance_discovery flag is set to true (by default), an instance_discovery is performed for authorityType = AAD and
208+
* hence, an exception is thrown while making a call to getMetaDataEntry() if instanceDiscoveryResponse is not mocked.
209+
*/
210+
@Test( dataProvider = "aadClouds",
211+
expectedExceptions = StringIndexOutOfBoundsException.class)
212+
public void aad_instance_discovery_true(String authority) throws Exception {
213+
214+
PublicClientApplication app = PublicClientApplication.builder("client_id")
215+
.authority(authority)
216+
.build();
217+
218+
AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(
219+
"code", new URI("http://my.redirect.com"))
220+
.scopes(Collections.singleton("scope")).build();
221+
222+
MsalRequest msalRequest = new AuthorizationCodeRequest(
223+
parameters,
224+
app,
225+
new RequestContext(app, PublicApi.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE, parameters));
226+
227+
URL authorityURL = new URL(authority);
228+
229+
AadInstanceDiscoveryProvider.getMetadataEntry(
230+
authorityURL,
231+
false,
232+
msalRequest,
233+
app.getServiceBundle());
234+
235+
}
236+
237+
/**
238+
* when instance_discovery flag is set to true (by default), an instance_discovery is NOT performed for b2c.
239+
*/
240+
@Test( dataProvider = "b2cAdfsClouds")
241+
public void b2c_adfs_instance_discovery_true(String authority) throws Exception {
242+
243+
PublicClientApplication app = PublicClientApplication.builder("client_id")
244+
.authority(authority)
245+
.build();
246+
247+
AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(
248+
"code", new URI("http://my.redirect.com"))
249+
.scopes(Collections.singleton("scope")).build();
250+
251+
MsalRequest msalRequest = new AuthorizationCodeRequest(
252+
parameters,
253+
app,
254+
new RequestContext(app, PublicApi.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE, parameters));
255+
256+
URL authorityURL = new URL(authority);
257+
258+
AadInstanceDiscoveryProvider.getMetadataEntry(
259+
authorityURL,
260+
false,
261+
msalRequest,
262+
app.getServiceBundle());
263+
}
264+
265+
@Test (dataProvider = "aadClouds")
266+
/**
267+
* when instance_discovery flag is set to false, instance_discovery is not performed and hence,
268+
* no exception is thrown while making a call to getMetaDataEntry() even when instanceDiscoveryResponse is not mocked.
269+
*/
270+
public void aad_instance_discovery_false(String authority) throws Exception{
271+
272+
PublicClientApplication app = PublicClientApplication.builder("client_id")
273+
.authority(authority)
274+
.instanceDiscovery(false)
275+
.build();
276+
277+
AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(
278+
"code", new URI("http://my.redirect.com"))
279+
.scopes(Collections.singleton("scope")).build();
280+
281+
MsalRequest msalRequest = new AuthorizationCodeRequest(
282+
parameters,
283+
app,
284+
new RequestContext(app, PublicApi.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE, parameters));
285+
286+
URL authorityURL = new URL(authority);
287+
288+
AadInstanceDiscoveryProvider.getMetadataEntry(
289+
authorityURL,
290+
false,
291+
msalRequest,
292+
app.getServiceBundle());
293+
}
294+
295+
@Test (dataProvider = "b2cAdfsClouds")
296+
/**
297+
* when instance_discovery flag is set to true, instance_discovery is not performed and hence,
298+
* no exception is thrown while making a call to getMetaDataEntry() even when instanceDiscoveryResponse is not mocked.
299+
*/
300+
public void b2c_adfs_instance_discovery_false(String authority) throws Exception{
301+
302+
PublicClientApplication app = PublicClientApplication.builder("client_id")
303+
.authority(authority)
304+
.instanceDiscovery(false)
305+
.build();
306+
307+
AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(
308+
"code", new URI("http://my.redirect.com"))
309+
.scopes(Collections.singleton("scope")).build();
310+
311+
MsalRequest msalRequest = new AuthorizationCodeRequest(
312+
parameters,
313+
app,
314+
new RequestContext(app, PublicApi.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE, parameters));
315+
316+
URL authorityURL = new URL(authority);
317+
318+
AadInstanceDiscoveryProvider.getMetadataEntry(
319+
authorityURL,
320+
false,
321+
msalRequest,
322+
app.getServiceBundle());
323+
}
189324
}

0 commit comments

Comments
 (0)