From ede1afc5f793009fa87c059ab3ecb84542c083d3 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Sun, 30 Aug 2020 21:32:58 -0700 Subject: [PATCH 01/11] Add 2019 MSI endpoint support. --- .../identity/AppServiceMsiCredential.java | 31 ++++++++++++----- .../identity/ManagedIdentityCredential.java | 5 ++- .../implementation/IdentityClient.java | 18 ++++++---- .../ManagedIdentityCredentialTest.java | 34 ++++++++++++++++++- .../implementation/IdentityClientTests.java | 25 +++++++++++++- 5 files changed, 94 insertions(+), 19 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java index b5a35e7545c94..26b624e501573 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java @@ -16,8 +16,9 @@ */ @Immutable class AppServiceMsiCredential { - private final String msiEndpoint; - private final String msiSecret; + private final String endpoint; + private final String secret; + private final String msiVersion; private final IdentityClient identityClient; private final String clientId; private final ClientLogger logger = new ClientLogger(AppServiceMsiCredential.class); @@ -30,14 +31,26 @@ class AppServiceMsiCredential { */ AppServiceMsiCredential(String clientId, IdentityClient identityClient) { Configuration configuration = Configuration.getGlobalConfiguration().clone(); - this.msiEndpoint = configuration.get(Configuration.PROPERTY_MSI_ENDPOINT); - this.msiSecret = configuration.get(Configuration.PROPERTY_MSI_SECRET); + if (configuration.contains(ManagedIdentityCredential.PROPERTY_IDENTITY_ENDPOINT)) { + this.endpoint = configuration.get(ManagedIdentityCredential.PROPERTY_IDENTITY_ENDPOINT); + this.secret = configuration.get(ManagedIdentityCredential.PROPERTY_IDENTITY_HEADER); + msiVersion = "2019-08-01"; + if (!(endpoint.startsWith("https") || endpoint.startsWith("http"))) { + throw logger.logExceptionAsError( + new IllegalArgumentException("Identity Endpoint should start with 'https' or 'http' scheme.")); + } + } else { + this.endpoint = configuration.get(Configuration.PROPERTY_MSI_ENDPOINT); + this.secret = configuration.get(Configuration.PROPERTY_MSI_SECRET); + msiVersion = "2017-09-01"; + if (!(endpoint.startsWith("https") || endpoint.startsWith("http"))) { + throw logger.logExceptionAsError( + new IllegalArgumentException("MSI Endpoint should start with 'https' or 'http' scheme.")); + } + } this.identityClient = identityClient; this.clientId = clientId; - if (!(msiEndpoint.startsWith("https") || msiEndpoint.startsWith("http"))) { - throw logger.logExceptionAsError( - new IllegalArgumentException("MSI Endpoint should start with 'https' or 'http' scheme.")); - } + } /** @@ -56,6 +69,6 @@ public String getClientId() { * @return A publisher that emits an {@link AccessToken}. */ public Mono authenticate(TokenRequestContext request) { - return identityClient.authenticateToManagedIdentityEndpoint(msiEndpoint, msiSecret, request); + return identityClient.authenticateToManagedIdentityEndpoint(endpoint, secret, msiVersion, request); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java index 69cf15ee04255..f68cd29136bfe 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java @@ -23,6 +23,8 @@ public final class ManagedIdentityCredential implements TokenCredential { private final AppServiceMsiCredential appServiceMSICredential; private final VirtualMachineMsiCredential virtualMachineMSICredential; private final ClientLogger logger = new ClientLogger(ManagedIdentityCredential.class); + static final String PROPERTY_IDENTITY_ENDPOINT = "IDENTITY_ENDPOINT"; + static final String PROPERTY_IDENTITY_HEADER = "IDENTITY_HEADER"; /** * Creates an instance of the ManagedIdentityCredential. @@ -35,7 +37,8 @@ public final class ManagedIdentityCredential implements TokenCredential { .identityClientOptions(identityClientOptions) .build(); Configuration configuration = Configuration.getGlobalConfiguration().clone(); - if (configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT)) { + if (configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT) + || configuration.contains(PROPERTY_IDENTITY_ENDPOINT)) { appServiceMSICredential = new AppServiceMsiCredential(clientId, identityClient); virtualMachineMSICredential = null; } else { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 5e05a5d5a3766..6638b94f684f6 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -727,12 +727,12 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext requ /** * Asynchronously acquire a token from the App Service Managed Service Identity endpoint. * - * @param msiEndpoint the endpoint to acquire token from - * @param msiSecret the secret to acquire token with + * @param endpoint the endpoint to acquire token from + * @param secret the secret to acquire token with * @param request the details of the token request * @return a Publisher that emits an AccessToken */ - public Mono authenticateToManagedIdentityEndpoint(String msiEndpoint, String msiSecret, + public Mono authenticateToManagedIdentityEndpoint(String endpoint, String secret, String version, TokenRequestContext request) { return Mono.fromCallable(() -> { String resource = ScopeUtil.scopesToResource(request.getScopes()); @@ -742,18 +742,22 @@ public Mono authenticateToManagedIdentityEndpoint(String msiEndpoin payload.append("resource="); payload.append(URLEncoder.encode(resource, "UTF-8")); payload.append("&api-version="); - payload.append(URLEncoder.encode("2017-09-01", "UTF-8")); + payload.append(URLEncoder.encode(version, "UTF-8")); if (clientId != null) { payload.append("&clientid="); payload.append(URLEncoder.encode(clientId, "UTF-8")); } try { - URL url = new URL(String.format("%s?%s", msiEndpoint, payload)); + URL url = new URL(String.format("%s?%s", endpoint, payload)); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); - if (msiSecret != null) { - connection.setRequestProperty("Secret", msiSecret); + if (secret != null) { + if (version.equals("2019-08-01")) { + connection.setRequestProperty("X-IDENTITY-HEADER", secret); + } else { + connection.setRequestProperty("Secret", secret); + } } connection.setRequestProperty("Metadata", "true"); diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java index 69ecb79ea47d1..fe88cda2d5feb 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java @@ -51,7 +51,7 @@ public void testMSIEndpoint() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateToManagedIdentityEndpoint(endpoint, secret, request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + when(identityClient.authenticateToManagedIdentityEndpoint(endpoint, secret, "2017-09-01", request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); // test @@ -67,6 +67,38 @@ public void testMSIEndpoint() throws Exception { } } + @Test + public void testIdentityEndpoint() throws Exception { + Configuration configuration = Configuration.getGlobalConfiguration(); + + try { + // setup + String endpoint = "http://localhost"; + String secret = "secret"; + String token1 = "token1"; + TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com"); + OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1); + configuration.put("IDENTITY_ENDPOINT", endpoint); + configuration.put("IDENTITY_HEADER", secret); + + // mock + IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); + when(identityClient.authenticateToManagedIdentityEndpoint(endpoint, secret, "2019-08-01", request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); + + // test + ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID).build(); + StepVerifier.create(credential.getToken(request1)) + .expectNextMatches(token -> token1.equals(token.getToken()) + && expiresAt.getSecond() == token.getExpiresAt().getSecond()) + .verifyComplete(); + } finally { + // clean up + configuration.remove("IDENTITY_ENDPOINT"); + configuration.remove("IDENTITY_HEADER"); + } + } + @Test public void testIMDS() throws Exception { // setup diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java index 4decdfd94044a..b56f4e82e36ca 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java @@ -200,7 +200,30 @@ public void testValidMSICodeFlow() throws Exception { // test IdentityClient client = new IdentityClientBuilder().build(); - AccessToken token = client.authenticateToManagedIdentityEndpoint(endpoint, secret, request).block(); + AccessToken token = client.authenticateToManagedIdentityEndpoint(endpoint, secret, "2017-09-01", request).block(); + Assert.assertEquals("token1", token.getToken()); + Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); + } + + @Test + public void testValidIdentityEndpointMSICodeFlow() throws Exception { + // setup + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = "http://localhost"; + String secret = "secret"; + TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com"); + OffsetDateTime expiresOn = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1); + configuration.put("IDENTITY_ENDPOINT", endpoint); + configuration.put("IDENTITY_HEADER", secret); + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("M/d/yyyy H:mm:ss XXX"); + String tokenJson = "{ \"access_token\" : \"token1\", \"expires_on\" : \"" + expiresOn.format(dtf) + "\" }"; + + // mock + mockForMSICodeFlow(tokenJson); + + // test + IdentityClient client = new IdentityClientBuilder().build(); + AccessToken token = client.authenticateToManagedIdentityEndpoint(endpoint, secret, "2019-08-01", request).block(); Assert.assertEquals("token1", token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); } From bd2580c0d1d7854f267d093fdc9e167867721ddc Mon Sep 17 00:00:00 2001 From: g2vinay Date: Tue, 1 Sep 2020 21:12:42 -0700 Subject: [PATCH 02/11] refactor code. --- .../identity/WebJobsIdentityTest.java | 28 ++++++++++-- .../identity/AppServiceMsiCredential.java | 43 ++++++++++--------- .../identity/ManagedIdentityCredential.java | 2 + .../implementation/IdentityClient.java | 35 +++++++++++---- .../ManagedIdentityCredentialTest.java | 4 +- .../implementation/IdentityClientTests.java | 4 +- 6 files changed, 79 insertions(+), 37 deletions(-) diff --git a/sdk/e2e/src/main/java/com/azure/endtoend/identity/WebJobsIdentityTest.java b/sdk/e2e/src/main/java/com/azure/endtoend/identity/WebJobsIdentityTest.java index abe1c4a277704..a9a36d9399f23 100644 --- a/sdk/e2e/src/main/java/com/azure/endtoend/identity/WebJobsIdentityTest.java +++ b/sdk/e2e/src/main/java/com/azure/endtoend/identity/WebJobsIdentityTest.java @@ -28,6 +28,8 @@ class WebJobsIdentityTest { private static final String AZURE_WEBJOBS_TEST_MODE = "AZURE_WEBJOBS_TEST_MODE"; private static final String AZURE_VAULT_URL = "AZURE_VAULT_URL"; private static final Configuration CONFIGURATION = Configuration.getGlobalConfiguration().clone(); + private static final String PROPERTY_IDENTITY_ENDPOINT = "IDENTITY_ENDPOINT"; + private static final String PROPERTY_IDENTITY_HEADER = "IDENTITY_HEADER"; private final ClientLogger logger = new ClientLogger(WebJobsIdentityTest.class); /** @@ -60,12 +62,18 @@ void run() throws IllegalStateException { } private void testMSIEndpointWithSystemAssigned() { - assertConfigPresence(Configuration.PROPERTY_MSI_ENDPOINT, - "testMSIEndpointWithSystemAssigned - MSIEndpoint not configured in the environment."); + if (CoreUtils.isNullOrEmpty(CONFIGURATION.get(Configuration.PROPERTY_MSI_ENDPOINT)) + && CoreUtils.isNullOrEmpty(CONFIGURATION.get(Configuration.PROPERTY_MSI_SECRET))) { + throw logger.logExceptionAsError( + new IllegalStateException("testMSIEndpointWithUserAssigned - MSIEndpoint and Identity Point not" + + "configured in the environment. Atleast one should be configuured")); + } assertConfigPresence(Configuration.PROPERTY_MSI_SECRET, "testMSIEndpointWithSystemAssigned - MSISecret not configured in the environment."); IdentityClient client = new IdentityClientBuilder().build(); AccessToken accessToken = client.authenticateToManagedIdentityEndpoint( + CONFIGURATION.get(PROPERTY_IDENTITY_ENDPOINT), + CONFIGURATION.get(PROPERTY_IDENTITY_HEADER), CONFIGURATION.get(Configuration.PROPERTY_MSI_ENDPOINT), CONFIGURATION.get(Configuration.PROPERTY_MSI_SECRET), new TokenRequestContext().addScopes("https://management.azure.com/.default")) @@ -115,8 +123,12 @@ private void testMSIEndpointWithSystemAssignedAccessKeyVault() { } private void testMSIEndpointWithUserAssigned() { - assertConfigPresence(Configuration.PROPERTY_MSI_ENDPOINT, - "testMSIEndpointWithUserAssigned - MSIEndpoint not configured in the environment."); + if (CoreUtils.isNullOrEmpty(CONFIGURATION.get(Configuration.PROPERTY_MSI_ENDPOINT)) + && CoreUtils.isNullOrEmpty(CONFIGURATION.get(Configuration.PROPERTY_MSI_SECRET))) { + throw logger.logExceptionAsError( + new IllegalStateException("testMSIEndpointWithUserAssigned - MSIEndpoint and Identity Point not" + + "configured in the environment. Atleast one should be configuured")); + } assertConfigPresence(Configuration.PROPERTY_AZURE_CLIENT_ID, "testMSIEndpointWithUserAssigned - Client is not configured in the environment."); assertConfigPresence(AZURE_VAULT_URL, @@ -127,6 +139,8 @@ private void testMSIEndpointWithUserAssigned() { .build(); AccessToken accessToken = client.authenticateToManagedIdentityEndpoint( + CONFIGURATION.get(PROPERTY_IDENTITY_ENDPOINT), + CONFIGURATION.get(PROPERTY_IDENTITY_HEADER), CONFIGURATION.get(Configuration.PROPERTY_MSI_ENDPOINT), CONFIGURATION.get(Configuration.PROPERTY_MSI_SECRET), new TokenRequestContext().addScopes("https://management.azure.com/.default")) @@ -150,6 +164,12 @@ private void testMSIEndpointWithUserAssigned() { } private void testMSIEndpointWithUserAssignedAccessKeyVault() { + + if (CoreUtils.isNullOrEmpty(CONFIGURATION.get(Configuration.PROPERTY_MSI_ENDPOINT) ) + && CoreUtils.isNullOrEmpty(CONFIGURATION.get("IDENTITY_ENDPOINT"))) { + + } + assertConfigPresence(Configuration.PROPERTY_MSI_ENDPOINT, "testMSIEndpointWithUserAssignedKeyVault - MSIEndpoint not configured in the environment."); assertConfigPresence(Configuration.PROPERTY_AZURE_CLIENT_ID, diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java index 26b624e501573..3ad074c54c838 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java @@ -16,9 +16,10 @@ */ @Immutable class AppServiceMsiCredential { - private final String endpoint; - private final String secret; - private final String msiVersion; + private final String identityEndpoint; + private final String msiEndpoint; + private final String msiSecret; + private final String identityHeader; private final IdentityClient identityClient; private final String clientId; private final ClientLogger logger = new ClientLogger(AppServiceMsiCredential.class); @@ -31,26 +32,25 @@ class AppServiceMsiCredential { */ AppServiceMsiCredential(String clientId, IdentityClient identityClient) { Configuration configuration = Configuration.getGlobalConfiguration().clone(); - if (configuration.contains(ManagedIdentityCredential.PROPERTY_IDENTITY_ENDPOINT)) { - this.endpoint = configuration.get(ManagedIdentityCredential.PROPERTY_IDENTITY_ENDPOINT); - this.secret = configuration.get(ManagedIdentityCredential.PROPERTY_IDENTITY_HEADER); - msiVersion = "2019-08-01"; - if (!(endpoint.startsWith("https") || endpoint.startsWith("http"))) { - throw logger.logExceptionAsError( - new IllegalArgumentException("Identity Endpoint should start with 'https' or 'http' scheme.")); - } - } else { - this.endpoint = configuration.get(Configuration.PROPERTY_MSI_ENDPOINT); - this.secret = configuration.get(Configuration.PROPERTY_MSI_SECRET); - msiVersion = "2017-09-01"; - if (!(endpoint.startsWith("https") || endpoint.startsWith("http"))) { - throw logger.logExceptionAsError( - new IllegalArgumentException("MSI Endpoint should start with 'https' or 'http' scheme.")); - } - } + this.identityEndpoint = configuration.get(ManagedIdentityCredential.PROPERTY_IDENTITY_ENDPOINT); + this.identityHeader = configuration.get(ManagedIdentityCredential.PROPERTY_IDENTITY_HEADER); + this.msiEndpoint = configuration.get(Configuration.PROPERTY_MSI_ENDPOINT); + this.msiSecret = configuration.get(Configuration.PROPERTY_MSI_SECRET); this.identityClient = identityClient; this.clientId = clientId; + if (identityEndpoint != null) { + validateEndpointProtocol(this.identityEndpoint, "Identity"); + } else { + validateEndpointProtocol(this.msiEndpoint, "MSI"); + } + } + private void validateEndpointProtocol(String endpoint, String endpointName) { + if (!(endpoint.startsWith("https") || endpoint.startsWith("http"))) { + throw logger.logExceptionAsError( + new IllegalArgumentException( + String.format("%s endpoint should start with 'https' or 'http' scheme.", endpointName))); + } } /** @@ -69,6 +69,7 @@ public String getClientId() { * @return A publisher that emits an {@link AccessToken}. */ public Mono authenticate(TokenRequestContext request) { - return identityClient.authenticateToManagedIdentityEndpoint(endpoint, secret, msiVersion, request); + return identityClient.authenticateToManagedIdentityEndpoint(identityEndpoint, identityHeader, msiEndpoint, + msiSecret, request); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java index f68cd29136bfe..c192bfbf206ea 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java @@ -23,6 +23,8 @@ public final class ManagedIdentityCredential implements TokenCredential { private final AppServiceMsiCredential appServiceMSICredential; private final VirtualMachineMsiCredential virtualMachineMSICredential; private final ClientLogger logger = new ClientLogger(ManagedIdentityCredential.class); + + // TODO: Migrate to Configuration class in Core (https://github.com/Azure/azure-sdk-for-java/issues/14720) static final String PROPERTY_IDENTITY_ENDPOINT = "IDENTITY_ENDPOINT"; static final String PROPERTY_IDENTITY_HEADER = "IDENTITY_HEADER"; diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 6638b94f684f6..a5497dc6080e5 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -111,6 +111,8 @@ public class IdentityClient { private static final String DEFAULT_CONFIDENTIAL_KEYRING_ITEM_NAME = DEFAULT_CONFIDENTIAL_KEYCHAIN_ACCOUNT; private static final String DEFAULT_KEYRING_ATTR_NAME = "MsalClientID"; private static final String DEFAULT_KEYRING_ATTR_VALUE = "Microsoft.Developer.IdentityService"; + public static final String IDENTITY_ENDPOINT_VERSION = "2019-08-01"; + public static final String MSI_ENDPOINT_VERSION = "2017-09-01"; private final ClientLogger logger = new ClientLogger(IdentityClient.class); private final IdentityClientOptions options; @@ -727,14 +729,31 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext requ /** * Asynchronously acquire a token from the App Service Managed Service Identity endpoint. * - * @param endpoint the endpoint to acquire token from - * @param secret the secret to acquire token with + * @param identityEndpoint the Identity endpoint to acquire token from + * @param identityHeader the identity header to acquire token with + * @param msiEndpoint the MSI endpoint to acquire token from + * @param msiSecret the msi secret to acquire token with * @param request the details of the token request * @return a Publisher that emits an AccessToken */ - public Mono authenticateToManagedIdentityEndpoint(String endpoint, String secret, String version, + public Mono authenticateToManagedIdentityEndpoint(String identityEndpoint, String identityHeader, + String msiEndpoint, String msiSecret, TokenRequestContext request) { return Mono.fromCallable(() -> { + String endpoint; + String headerValue; + String endpointVersion; + + if (identityEndpoint != null) { + endpoint = identityEndpoint; + headerValue = identityHeader; + endpointVersion = IDENTITY_ENDPOINT_VERSION; + } else { + endpoint = msiEndpoint; + headerValue = msiSecret; + endpointVersion = MSI_ENDPOINT_VERSION; + } + String resource = ScopeUtil.scopesToResource(request.getScopes()); HttpURLConnection connection = null; StringBuilder payload = new StringBuilder(); @@ -742,7 +761,7 @@ public Mono authenticateToManagedIdentityEndpoint(String endpoint, payload.append("resource="); payload.append(URLEncoder.encode(resource, "UTF-8")); payload.append("&api-version="); - payload.append(URLEncoder.encode(version, "UTF-8")); + payload.append(URLEncoder.encode(endpointVersion, "UTF-8")); if (clientId != null) { payload.append("&clientid="); payload.append(URLEncoder.encode(clientId, "UTF-8")); @@ -752,11 +771,11 @@ public Mono authenticateToManagedIdentityEndpoint(String endpoint, connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); - if (secret != null) { - if (version.equals("2019-08-01")) { - connection.setRequestProperty("X-IDENTITY-HEADER", secret); + if (headerValue != null) { + if (endpointVersion.equals(IDENTITY_ENDPOINT_VERSION)) { + connection.setRequestProperty("X-IDENTITY-HEADER", headerValue); } else { - connection.setRequestProperty("Secret", secret); + connection.setRequestProperty("Secret", headerValue); } } connection.setRequestProperty("Metadata", "true"); diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java index fe88cda2d5feb..6a9caa0e8688a 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java @@ -51,7 +51,7 @@ public void testMSIEndpoint() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateToManagedIdentityEndpoint(endpoint, secret, "2017-09-01", request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + when(identityClient.authenticateToManagedIdentityEndpoint(null, null, endpoint, secret, request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); // test @@ -83,7 +83,7 @@ public void testIdentityEndpoint() throws Exception { // mock IdentityClient identityClient = PowerMockito.mock(IdentityClient.class); - when(identityClient.authenticateToManagedIdentityEndpoint(endpoint, secret, "2019-08-01", request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + when(identityClient.authenticateToManagedIdentityEndpoint(endpoint, secret, null, null, request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); PowerMockito.whenNew(IdentityClient.class).withAnyArguments().thenReturn(identityClient); // test diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java index b56f4e82e36ca..959eb40e0bacb 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java @@ -200,7 +200,7 @@ public void testValidMSICodeFlow() throws Exception { // test IdentityClient client = new IdentityClientBuilder().build(); - AccessToken token = client.authenticateToManagedIdentityEndpoint(endpoint, secret, "2017-09-01", request).block(); + AccessToken token = client.authenticateToManagedIdentityEndpoint(null, null, endpoint, secret, request).block(); Assert.assertEquals("token1", token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); } @@ -223,7 +223,7 @@ public void testValidIdentityEndpointMSICodeFlow() throws Exception { // test IdentityClient client = new IdentityClientBuilder().build(); - AccessToken token = client.authenticateToManagedIdentityEndpoint(endpoint, secret, "2019-08-01", request).block(); + AccessToken token = client.authenticateToManagedIdentityEndpoint(endpoint, secret, null, null, request).block(); Assert.assertEquals("token1", token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); } From ad9bcdb4cfa6aaff4c1e61e4ac26dcd09ec33b1c Mon Sep 17 00:00:00 2001 From: g2vinay Date: Wed, 2 Sep 2020 11:11:14 -0700 Subject: [PATCH 03/11] update e2e --- .../identity/ManagedIdentityCredentialLiveTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdk/e2e/src/test/java/com/azure/endtoend/identity/ManagedIdentityCredentialLiveTest.java b/sdk/e2e/src/test/java/com/azure/endtoend/identity/ManagedIdentityCredentialLiveTest.java index ece901c930ad5..96b4592d18174 100644 --- a/sdk/e2e/src/test/java/com/azure/endtoend/identity/ManagedIdentityCredentialLiveTest.java +++ b/sdk/e2e/src/test/java/com/azure/endtoend/identity/ManagedIdentityCredentialLiveTest.java @@ -25,6 +25,8 @@ public class ManagedIdentityCredentialLiveTest { private static final String AZURE_VAULT_URL = "AZURE_VAULT_URL"; private static final String VAULT_SECRET_NAME = "secret"; private static final Configuration CONFIGURATION = Configuration.getGlobalConfiguration().clone(); + private static final String PROPERTY_IDENTITY_ENDPOINT = "IDENTITY_ENDPOINT"; + private static final String PROPERTY_IDENTITY_HEADER = "IDENTITY_HEADER"; @Test public void testMSIEndpointWithSystemAssigned() throws Exception { @@ -34,6 +36,8 @@ public void testMSIEndpointWithSystemAssigned() throws Exception { IdentityClient client = new IdentityClientBuilder().build(); StepVerifier.create(client.authenticateToManagedIdentityEndpoint( + CONFIGURATION.get(PROPERTY_IDENTITY_ENDPOINT), + CONFIGURATION.get(PROPERTY_IDENTITY_HEADER), CONFIGURATION.get(Configuration.PROPERTY_MSI_ENDPOINT), CONFIGURATION.get(Configuration.PROPERTY_MSI_SECRET), new TokenRequestContext().addScopes("https://management.azure.com/.default"))) @@ -70,6 +74,8 @@ public void testMSIEndpointWithUserAssigned() throws Exception { .clientId(CONFIGURATION.get(Configuration.PROPERTY_AZURE_CLIENT_ID)) .build(); StepVerifier.create(client.authenticateToManagedIdentityEndpoint( + CONFIGURATION.get(PROPERTY_IDENTITY_ENDPOINT), + CONFIGURATION.get(PROPERTY_IDENTITY_HEADER), CONFIGURATION.get(Configuration.PROPERTY_MSI_ENDPOINT), CONFIGURATION.get(Configuration.PROPERTY_MSI_SECRET), new TokenRequestContext().addScopes("https://management.azure.com/.default"))) From 1643e7d07d43e06c9748be7f6243e493b3e509d4 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Thu, 3 Sep 2020 16:19:46 -0700 Subject: [PATCH 04/11] address feedback. --- .../java/com/azure/identity/AppServiceMsiCredential.java | 3 ++- .../java/com/azure/identity/ManagedIdentityCredential.java | 6 ++++-- .../com/azure/identity/implementation/IdentityClient.java | 6 +++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java index 3ad074c54c838..26a8a21980cb5 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java @@ -40,7 +40,8 @@ class AppServiceMsiCredential { this.clientId = clientId; if (identityEndpoint != null) { validateEndpointProtocol(this.identityEndpoint, "Identity"); - } else { + } + if (msiEndpoint != null) { validateEndpointProtocol(this.msiEndpoint, "MSI"); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java index c192bfbf206ea..43731a986663f 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java @@ -39,8 +39,10 @@ public final class ManagedIdentityCredential implements TokenCredential { .identityClientOptions(identityClientOptions) .build(); Configuration configuration = Configuration.getGlobalConfiguration().clone(); - if (configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT) - || configuration.contains(PROPERTY_IDENTITY_ENDPOINT)) { + if ((configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT) && + configuration.contains(Configuration.PROPERTY_MSI_SECRET)) + || (configuration.contains(PROPERTY_IDENTITY_ENDPOINT) && + configuration.contains(PROPERTY_IDENTITY_HEADER))) { appServiceMSICredential = new AppServiceMsiCredential(clientId, identityClient); virtualMachineMSICredential = null; } else { diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index fda5c2ca50dd9..568582ce8a5eb 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -745,7 +745,11 @@ public Mono authenticateToManagedIdentityEndpoint(String identityEn payload.append("&api-version="); payload.append(URLEncoder.encode(endpointVersion, "UTF-8")); if (clientId != null) { - payload.append("&clientid="); + if (endpointVersion.equals(IDENTITY_ENDPOINT_VERSION)) { + payload.append("&client_id="); + } else { + payload.append("&clientid="); + } payload.append(URLEncoder.encode(clientId, "UTF-8")); } try { From e7cb212c96ce4c1d202d8aca6af9210c2dc90e7e Mon Sep 17 00:00:00 2001 From: g2vinay Date: Thu, 3 Sep 2020 16:29:58 -0700 Subject: [PATCH 05/11] update code. --- .../java/com/azure/identity/ManagedIdentityCredential.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java index 43731a986663f..5c74b2ac43997 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java @@ -39,8 +39,7 @@ public final class ManagedIdentityCredential implements TokenCredential { .identityClientOptions(identityClientOptions) .build(); Configuration configuration = Configuration.getGlobalConfiguration().clone(); - if ((configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT) && - configuration.contains(Configuration.PROPERTY_MSI_SECRET)) + if (configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT) || (configuration.contains(PROPERTY_IDENTITY_ENDPOINT) && configuration.contains(PROPERTY_IDENTITY_HEADER))) { appServiceMSICredential = new AppServiceMsiCredential(clientId, identityClient); From c4646cf82f3e0cb3cb767e9760e5327d0b2a6f37 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Fri, 11 Sep 2020 10:19:24 -0700 Subject: [PATCH 06/11] update changelog --- sdk/identity/azure-identity/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index e878d13c9c792..5be6b30a00106 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.2.0-beta.1 (2020-09-11) - Added `InteractiveBrowserCredentialBuilder.redirectUrl(String)` to configure the redirect URL - Deprecated `InteractiveBrowserCredentialBuilder.port(int)` +- Added support for App Service 2019 MSI Endpoint in `ManagedIdentityCredential` - Added Shared Token cache support for MacOS Keychain, Gnome Keyring, and plain text for other Linux environments - Added option to write to shared token cache from `InteractiveBrowserCredential`, `AuthorizationCodeCredential`, `UsernamePasswordCredential`, `DeviceCodeCredential` `ClientSecretCredential` and `ClientCertificateCredential` - Added new APIs for authenticating users with `DeviceCodeCredential`, `InteractiveBrowserCredential` and `UsernamePasswordCredential`. From 04dd4838936bea3447e617c633b222c12b19494f Mon Sep 17 00:00:00 2001 From: g2vinay Date: Thu, 17 Sep 2020 10:48:32 -0700 Subject: [PATCH 07/11] add default console output behavior --- .../java/com/azure/identity/DeviceCodeCredentialBuilder.java | 1 - .../com/azure/identity/implementation/IdentityClient.java | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java index 9a9ea8d8c944d..506ba506a0ffb 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java @@ -88,7 +88,6 @@ public DeviceCodeCredentialBuilder disableAutomaticAuthentication() { public DeviceCodeCredential build() { ValidationUtil.validate(getClass().getSimpleName(), new HashMap() {{ put("clientId", clientId); - put("challengeConsumer", challengeConsumer); }}); return new DeviceCodeCredential(clientId, tenantId, challengeConsumer, automaticAuthentication, identityClientOptions); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 62e073f6c619d..a68f40827895a 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -580,6 +580,11 @@ public Mono authenticateWithConfidentialClientCache(TokenRequestCon */ public Mono authenticateWithDeviceCode(TokenRequestContext request, Consumer deviceCodeConsumer) { + + Consumer deviceCodeInfoConsumer = deviceCodeConsumer; + if (deviceCodeInfoConsumer == null) { + deviceCodeInfoConsumer = deviceCodeInfo -> System.out.println(deviceCodeInfo.getMessage()); + } return publicClientApplicationAccessor.getValue().flatMap(pc -> Mono.fromFuture(() -> { DeviceCodeFlowParameters parameters = DeviceCodeFlowParameters.builder( From 027a9f2a019b2457f3a503063bf8288de488889e Mon Sep 17 00:00:00 2001 From: g2vinay Date: Thu, 17 Sep 2020 11:39:58 -0700 Subject: [PATCH 08/11] update code --- .../com/azure/identity/implementation/IdentityClient.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index a68f40827895a..dc8cb4042e93b 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -581,14 +581,16 @@ public Mono authenticateWithConfidentialClientCache(TokenRequestCon public Mono authenticateWithDeviceCode(TokenRequestContext request, Consumer deviceCodeConsumer) { - Consumer deviceCodeInfoConsumer = deviceCodeConsumer; - if (deviceCodeInfoConsumer == null) { + final Consumer deviceCodeInfoConsumer; + if (deviceCodeConsumer == null) { + deviceCodeInfoConsumer = deviceCodeConsumer; + } else { deviceCodeInfoConsumer = deviceCodeInfo -> System.out.println(deviceCodeInfo.getMessage()); } return publicClientApplicationAccessor.getValue().flatMap(pc -> Mono.fromFuture(() -> { DeviceCodeFlowParameters parameters = DeviceCodeFlowParameters.builder( - new HashSet<>(request.getScopes()), dc -> deviceCodeConsumer.accept( + new HashSet<>(request.getScopes()), dc -> deviceCodeInfoConsumer.accept( new DeviceCodeInfo(dc.userCode(), dc.deviceCode(), dc.verificationUri(), OffsetDateTime.now().plusSeconds(dc.expiresIn()), dc.message()))).build(); return pc.acquireToken(parameters); From 560e8081a5516535ba6f9cd89b6a940ca2115496 Mon Sep 17 00:00:00 2001 From: g2vinay Date: Thu, 17 Sep 2020 11:51:33 -0700 Subject: [PATCH 09/11] update code --- .../java/com/azure/identity/implementation/IdentityClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index dc8cb4042e93b..a125e76f24f0a 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -582,7 +582,7 @@ public Mono authenticateWithDeviceCode(TokenRequestContext request, Consumer deviceCodeConsumer) { final Consumer deviceCodeInfoConsumer; - if (deviceCodeConsumer == null) { + if (deviceCodeConsumer != null) { deviceCodeInfoConsumer = deviceCodeConsumer; } else { deviceCodeInfoConsumer = deviceCodeInfo -> System.out.println(deviceCodeInfo.getMessage()); From 418737914e44e748e2a8fecbace2730f247a4f4e Mon Sep 17 00:00:00 2001 From: g2vinay Date: Fri, 25 Sep 2020 16:45:05 -0700 Subject: [PATCH 10/11] address feedback --- .../com/azure/identity/DeviceCodeCredentialBuilder.java | 8 ++++++-- .../azure/identity/implementation/IdentityClient.java | 9 +-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java index 506ba506a0ffb..b7ef8d88764e4 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/DeviceCodeCredentialBuilder.java @@ -15,11 +15,14 @@ * @see DeviceCodeCredential */ public class DeviceCodeCredentialBuilder extends AadCredentialBuilderBase { - private Consumer challengeConsumer; + private Consumer challengeConsumer = + deviceCodeInfo -> System.out.println(deviceCodeInfo.getMessage()); + private boolean automaticAuthentication = true; /** - * Sets the consumer to meet the device code challenge. + * Sets the consumer to meet the device code challenge. If not specified a default consumer is used which prints + * the device code info message to stdout. * * @param challengeConsumer A method allowing the user to meet the device code challenge. * @return the InteractiveBrowserCredentialBuilder itself @@ -88,6 +91,7 @@ public DeviceCodeCredentialBuilder disableAutomaticAuthentication() { public DeviceCodeCredential build() { ValidationUtil.validate(getClass().getSimpleName(), new HashMap() {{ put("clientId", clientId); + put("challengeConsumer", challengeConsumer); }}); return new DeviceCodeCredential(clientId, tenantId, challengeConsumer, automaticAuthentication, identityClientOptions); diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index a125e76f24f0a..62e073f6c619d 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -580,17 +580,10 @@ public Mono authenticateWithConfidentialClientCache(TokenRequestCon */ public Mono authenticateWithDeviceCode(TokenRequestContext request, Consumer deviceCodeConsumer) { - - final Consumer deviceCodeInfoConsumer; - if (deviceCodeConsumer != null) { - deviceCodeInfoConsumer = deviceCodeConsumer; - } else { - deviceCodeInfoConsumer = deviceCodeInfo -> System.out.println(deviceCodeInfo.getMessage()); - } return publicClientApplicationAccessor.getValue().flatMap(pc -> Mono.fromFuture(() -> { DeviceCodeFlowParameters parameters = DeviceCodeFlowParameters.builder( - new HashSet<>(request.getScopes()), dc -> deviceCodeInfoConsumer.accept( + new HashSet<>(request.getScopes()), dc -> deviceCodeConsumer.accept( new DeviceCodeInfo(dc.userCode(), dc.deviceCode(), dc.verificationUri(), OffsetDateTime.now().plusSeconds(dc.expiresIn()), dc.message()))).build(); return pc.acquireToken(parameters); From 8a5b8ff54125b2db5358bb7ef3539cffab5421ea Mon Sep 17 00:00:00 2001 From: g2vinay Date: Mon, 28 Sep 2020 09:39:48 -0700 Subject: [PATCH 11/11] add checkstyle sup --- .../main/resources/checkstyle/checkstyle-suppressions.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml index d3c02ee0484b2..150a51d358719 100755 --- a/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml +++ b/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml @@ -270,6 +270,11 @@ + + + +