From 122f5820ed650db7efb23681f3ae5299a5a4498e Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Fri, 23 Sep 2016 16:56:14 -0700 Subject: [PATCH 1/3] Squashed 'runtimes/' changes from 3010c5c..64c3003 64c3003 Merge pull request #54 from Azure/sdk_1045 a29b72b Merge pull request #53 from Azure/sdk_1060 524db4a Merge pull request #42 from Azure/sdk_1008 ffab45d Merge pull request #1060 from jianghaolu/unwrap 626b093 Merge ad3a89b71d8f43c5ee1d6fdf6061dbf0fcaae2be into cc5fdb3c5fbb5c05471fb1508143a64f1c116dc3 9be12ef Fix errors from merge 9732907 Merge commit '14c789805ffe68c1ced5dbe0f71448095216fcc1' into unwrap 0a85d84 applyAsync on a root resource should create a dependency resource only if it is not already created and root resource always needs to be updated in this case 65a9fd6 Merge f8a29d68d04bda404147a146537b244278817aa3 into ae008552a564ae6e177ebf83077de1c6b48c8da9 3c266c4 Add graph tests git-subtree-dir: runtimes git-subtree-split: 64c3003dddbeb4496621e6200da90a09cd296fd7 --- .../ApplicationTokenCredentials.java | 28 +++++++++++++++- .../credentials/UserTokenCredentials.java | 32 +++++++++++++++++- .../com/microsoft/azure/AzureEnvironment.java | 2 +- .../com/microsoft/azure/AzureServiceCall.java | 5 ++- .../com/microsoft/azure/TaskGroupBase.java | 33 +++++++++++++------ .../java/com/microsoft/rest/ServiceCall.java | 12 ++++++- 6 files changed, 95 insertions(+), 17 deletions(-) diff --git a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java index 384ab3f7671a..abe3a65c9bdd 100644 --- a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java +++ b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java @@ -24,6 +24,8 @@ * Token based credentials for use with a REST Service Client. */ public class ApplicationTokenCredentials extends TokenCredentials { + /** The endpoint of the target resource. */ + private String resourceEndpoint; /** The active directory application client id. */ private String clientId; /** The tenant or domain the containing the application. */ @@ -56,6 +58,30 @@ public ApplicationTokenCredentials(String clientId, String domain, String secret } else { this.environment = environment; } + this.resourceEndpoint = this.environment.getTokenAudience(); + } + + /** + * Initializes a new instance of the UserTokenCredentials. + * + * @param clientId the active directory application client id. + * @param domain the domain or tenant id containing this application. + * @param secret the authentication secret for the application. + * @param resourceEndpoint the endpoint of the target resource. + * @param environment the Azure environment to authenticate with. + * If null is provided, AzureEnvironment.AZURE will be used. + */ + public ApplicationTokenCredentials(String clientId, String domain, String secret, String resourceEndpoint, AzureEnvironment environment) { + super(null, null); // defer token acquisition + this.clientId = clientId; + this.domain = domain; + this.secret = secret; + this.resourceEndpoint = resourceEndpoint; + if (environment == null) { + this.environment = AzureEnvironment.AZURE; + } else { + this.environment = environment; + } } /** @@ -212,7 +238,7 @@ private void acquireAccessToken() throws IOException { AuthenticationContext context = new AuthenticationContext(authorityUrl, this.getEnvironment().isValidateAuthority(), executor); try { authenticationResult = context.acquireToken( - this.getEnvironment().getTokenAudience(), + this.resourceEndpoint, new ClientCredential(this.getClientId(), this.getSecret()), null).get(); } catch (Exception e) { diff --git a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java index 60d6bdb3c0a5..c37479771a9b 100644 --- a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java +++ b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java @@ -21,6 +21,8 @@ * Token based credentials for use with a REST Service Client. */ public class UserTokenCredentials extends TokenCredentials { + /** The endpoint of the target resource. */ + private String resourceEndpoint; /** The Active Directory application client id. */ private String clientId; /** The domain or tenant id containing this application. */ @@ -59,6 +61,34 @@ public UserTokenCredentials(String clientId, String domain, String username, Str } else { this.environment = environment; } + this.resourceEndpoint = this.environment.getTokenAudience(); + } + + /** + * Initializes a new instance of the UserTokenCredentials. + * + * @param clientId the active directory application client id. + * @param domain the domain or tenant id containing this application. + * @param username the user name for the Organization Id account. + * @param password the password for the Organization Id account. + * @param clientRedirectUri the Uri where the user will be redirected after authenticating with AD. + * @param resourceEndpoint the endpoint of the target resource. + * @param environment the Azure environment to authenticate with. + * If null is provided, AzureEnvironment.AZURE will be used. + */ + public UserTokenCredentials(String clientId, String domain, String username, String password, String clientRedirectUri, String resourceEndpoint, AzureEnvironment environment) { + super(null, null); // defer token acquisition + this.clientId = clientId; + this.domain = domain; + this.username = username; + this.password = password; + this.clientRedirectUri = clientRedirectUri; + this.resourceEndpoint = resourceEndpoint; + if (environment == null) { + this.environment = AzureEnvironment.AZURE; + } else { + this.environment = environment; + } } /** @@ -136,7 +166,7 @@ private void acquireAccessToken() throws IOException { AuthenticationContext context = new AuthenticationContext(authorityUrl, this.getEnvironment().isValidateAuthority(), Executors.newSingleThreadExecutor()); try { authenticationResult = context.acquireToken( - this.getEnvironment().getTokenAudience(), + this.resourceEndpoint, this.getClientId(), this.getUsername(), this.getPassword(), diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java index 04a6af99d93d..897e29b622c0 100644 --- a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java @@ -56,7 +56,7 @@ public AzureEnvironment( * Provides the settings for authentication with Azure. */ public static final AzureEnvironment AZURE = new AzureEnvironment( - "https://login.windows.net/", + "https://login.microsoftonline.com/", "https://management.core.windows.net/", true, "https://management.azure.com/"); diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureServiceCall.java b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureServiceCall.java index e9c8728b67f9..c55c189ceea4 100644 --- a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureServiceCall.java +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureServiceCall.java @@ -10,13 +10,12 @@ import com.microsoft.rest.ServiceCall; import com.microsoft.rest.ServiceResponse; import com.microsoft.rest.ServiceResponseWithHeaders; - -import java.util.List; - import rx.Observable; import rx.Subscriber; import rx.functions.Func1; +import java.util.List; + /** * An instance of this class provides access to the underlying REST call invocation. * This class wraps around the Retrofit Call object and allows updates to it in the diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/TaskGroupBase.java b/azure-client-runtime/src/main/java/com/microsoft/azure/TaskGroupBase.java index 747f0273fa7d..6de2a3ab4654 100644 --- a/azure-client-runtime/src/main/java/com/microsoft/azure/TaskGroupBase.java +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/TaskGroupBase.java @@ -64,18 +64,31 @@ public Observable executeAsync() { final List> observables = new ArrayList<>(); while (nextNode != null) { final DAGNode thisNode = nextNode; - observables.add(nextNode.data().executeAsync() - .flatMap(new Func1>() { - @Override - public Observable call(T t) { - dag().reportedCompleted(thisNode); - if (dag().isRootNode(thisNode)) { - return Observable.just(t); - } else { + T cachedResult = nextNode.data().result(); + if (cachedResult != null && !this.dag().isRootNode(nextNode)) { + observables.add(Observable.just(cachedResult) + .flatMap(new Func1>() { + @Override + public Observable call(T t) { + dag().reportedCompleted(thisNode); return executeAsync(); } - } - })); + }) + ); + } else { + observables.add(nextNode.data().executeAsync() + .flatMap(new Func1>() { + @Override + public Observable call(T t) { + dag().reportedCompleted(thisNode); + if (dag().isRootNode(thisNode)) { + return Observable.just(t); + } else { + return executeAsync(); + } + } + })); + } nextNode = dag.getNext(); } return Observable.merge(observables); diff --git a/client-runtime/src/main/java/com/microsoft/rest/ServiceCall.java b/client-runtime/src/main/java/com/microsoft/rest/ServiceCall.java index 949ef7b8879f..960a016b88dd 100644 --- a/client-runtime/src/main/java/com/microsoft/rest/ServiceCall.java +++ b/client-runtime/src/main/java/com/microsoft/rest/ServiceCall.java @@ -8,7 +8,6 @@ package com.microsoft.rest; import com.google.common.util.concurrent.AbstractFuture; - import rx.Observable; import rx.Subscription; import rx.functions.Action1; @@ -130,6 +129,17 @@ protected void setSubscription(Subscription subscription) { this.subscription = subscription; } + /** + * Invoke this method to report completed, allowing + * {@link AbstractFuture#get()} to be unblocked. + * + * @param result the service response returned. + * @return true if successfully reported; false otherwise. + */ + public boolean success(T result) { + return set(result); + } + @Override public boolean cancel(boolean mayInterruptIfRunning) { subscription.unsubscribe(); From 1d9a3df69051250f68fa4a9aecd7322cf06ceb8c Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Sun, 25 Sep 2016 20:01:19 -0500 Subject: [PATCH 2/3] Require graph endpoint in authentication file --- .../azure/credentials/ApplicationTokenCredentials.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/runtimes/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java b/runtimes/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java index 48b4a061018e..dc5ef9dc7d3b 100644 --- a/runtimes/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java +++ b/runtimes/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java @@ -76,7 +76,9 @@ private enum CredentialSettings { /** The base URL to the current Azure environment. */ BASE_URL("baseURL"), /** The URL to Active Directory authentication. */ - AUTH_URL("authURL"); + AUTH_URL("authURL"), + /** The URL to Active Directory Graph. */ + GRAPH_URL("graphURL"); /** The name of the key in the properties file. */ private final String name; @@ -143,6 +145,7 @@ public static ApplicationTokenCredentials fromFile(File credentialsFile) throws final String mgmtUri = authSettings.getProperty(CredentialSettings.MANAGEMENT_URI.toString()); final String authUrl = authSettings.getProperty(CredentialSettings.AUTH_URL.toString()); final String baseUrl = authSettings.getProperty(CredentialSettings.BASE_URL.toString()); + final String graphUrl = authSettings.getProperty(CredentialSettings.GRAPH_URL.toString()); final String defaultSubscriptionId = authSettings.getProperty(CredentialSettings.SUBSCRIPTION_ID.toString()); return new ApplicationTokenCredentials( @@ -153,7 +156,7 @@ public static ApplicationTokenCredentials fromFile(File credentialsFile) throws authUrl, mgmtUri, baseUrl, - "https://graph.windows.net/") // TODO: cred file should contain GRAPH endpoint + graphUrl) ).withDefaultSubscriptionId(defaultSubscriptionId); } From 7997eaa0acff7c15a6954016e4ea7ffbe248bf33 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Sun, 25 Sep 2016 21:33:30 -0500 Subject: [PATCH 3/3] Squashed 'runtimes/' changes from 64c3003..553fa4c 553fa4c Merge commit '122f5820ed650db7efb23681f3ae5299a5a4498e' 163b389 Fix error constructor call in mapper adapter d67ca69 Merge pull request #1093 from jianghaolu/validatorfix 00b45db Fix bug in validator that stack overflows on final/static fields 245aba2 [Ready for review] Redis Client implementation (#1057) 29bcf65 Let's not break everyone with paged list yet 0eb2adc Remove throws on many methods 26d2c0a Fix merge errors 2bbd23a Add javadocs and address checkstyle 12cd83e Adapt group paged list to new paged list bc575ae Allow empty pages in paged list 7b325e2 Add tenantId requirement on roll-up client 7d1f0d8 Enable user login c25c32a Supports spn & upn config in vault creation ac061a5 Allow specifying upn and spn 89fc914 Regen graph and a bunch of refactors e6c05e1 Add group & add graph endpoints to AzureEnvironment 61de8e9 Merge commit 'a1fc2e81ba8724105c79f97d1697dc75ea81521c' d5a3639 Add graph tests git-subtree-dir: runtimes git-subtree-split: 553fa4c5a19cf759009743e25b2cd70a475e9703 --- .../ApplicationTokenCredentials.java | 88 +++++--------- .../credentials/UserTokenCredentials.java | 110 ++++++------------ .../UserTokenCredentialsTests.java | 5 +- .../com/microsoft/azure/AzureEnvironment.java | 81 ++++++++----- .../java/com/microsoft/azure/PagedList.java | 59 ++++++---- .../credentials/AzureTokenCredentials.java | 40 +++++++ .../AzureTokenCredentialsInterceptor.java | 59 ++++++++++ .../azure/credentials/package-info.java | 6 + .../com/microsoft/azure/PagedListTests.java | 17 +-- .../rest/ServiceResponseBuilder.java | 9 +- .../java/com/microsoft/rest/Validator.java | 6 + .../serializer/FlatteningDeserializer.java | 33 +++--- 12 files changed, 300 insertions(+), 213 deletions(-) create mode 100644 azure-client-runtime/src/main/java/com/microsoft/azure/credentials/AzureTokenCredentials.java create mode 100644 azure-client-runtime/src/main/java/com/microsoft/azure/credentials/AzureTokenCredentialsInterceptor.java create mode 100644 azure-client-runtime/src/main/java/com/microsoft/azure/credentials/package-info.java diff --git a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java index abe3a65c9bdd..48b4a061018e 100644 --- a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java +++ b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/ApplicationTokenCredentials.java @@ -12,10 +12,14 @@ import com.microsoft.aad.adal4j.ClientCredential; import com.microsoft.azure.AzureEnvironment; import com.microsoft.rest.credentials.TokenCredentials; +import okhttp3.OkHttpClient; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -23,19 +27,17 @@ /** * Token based credentials for use with a REST Service Client. */ -public class ApplicationTokenCredentials extends TokenCredentials { - /** The endpoint of the target resource. */ - private String resourceEndpoint; +public class ApplicationTokenCredentials extends TokenCredentials implements AzureTokenCredentials { + /** A mapping from resource endpoint to its cached access token. */ + private Map tokens; + /** The Azure environment to authenticate with. */ + private AzureEnvironment environment; /** The active directory application client id. */ private String clientId; /** The tenant or domain the containing the application. */ private String domain; /** The authentication secret for the application. */ private String secret; - /** The Azure environment to authenticate with. */ - private AzureEnvironment environment; - /** The current authentication result. */ - private AuthenticationResult authenticationResult; /** The default subscription to use, if any. */ private String defaultSubscription; @@ -50,38 +52,11 @@ public class ApplicationTokenCredentials extends TokenCredentials { */ public ApplicationTokenCredentials(String clientId, String domain, String secret, AzureEnvironment environment) { super(null, null); // defer token acquisition + this.environment = environment; this.clientId = clientId; this.domain = domain; this.secret = secret; - if (environment == null) { - this.environment = AzureEnvironment.AZURE; - } else { - this.environment = environment; - } - this.resourceEndpoint = this.environment.getTokenAudience(); - } - - /** - * Initializes a new instance of the UserTokenCredentials. - * - * @param clientId the active directory application client id. - * @param domain the domain or tenant id containing this application. - * @param secret the authentication secret for the application. - * @param resourceEndpoint the endpoint of the target resource. - * @param environment the Azure environment to authenticate with. - * If null is provided, AzureEnvironment.AZURE will be used. - */ - public ApplicationTokenCredentials(String clientId, String domain, String secret, String resourceEndpoint, AzureEnvironment environment) { - super(null, null); // defer token acquisition - this.clientId = clientId; - this.domain = domain; - this.secret = secret; - this.resourceEndpoint = resourceEndpoint; - if (environment == null) { - this.environment = AzureEnvironment.AZURE; - } else { - this.environment = environment; - } + this.tokens = new HashMap<>(); } /** @@ -155,7 +130,7 @@ public static ApplicationTokenCredentials fromFile(File credentialsFile) throws Properties authSettings = new Properties(); authSettings.put(CredentialSettings.AUTH_URL.toString(), AzureEnvironment.AZURE.getAuthenticationEndpoint()); authSettings.put(CredentialSettings.BASE_URL.toString(), AzureEnvironment.AZURE.getBaseUrl()); - authSettings.put(CredentialSettings.MANAGEMENT_URI.toString(), AzureEnvironment.AZURE.getTokenAudience()); + authSettings.put(CredentialSettings.MANAGEMENT_URI.toString(), AzureEnvironment.AZURE.getManagementEndpoint()); // Load the credentials from the file FileInputStream credentialsFileStream = new FileInputStream(credentialsFile); @@ -177,8 +152,8 @@ public static ApplicationTokenCredentials fromFile(File credentialsFile) throws new AzureEnvironment( authUrl, mgmtUri, - true, - baseUrl) + baseUrl, + "https://graph.windows.net/") // TODO: cred file should contain GRAPH endpoint ).withDefaultSubscriptionId(defaultSubscriptionId); } @@ -196,6 +171,7 @@ public String getClientId() { * * @return the tenant or domain the containing the application. */ + @Override public String getDomain() { return domain; } @@ -209,42 +185,40 @@ public String getSecret() { return secret; } - /** - * Gets the Azure environment to authenticate with. - * - * @return the Azure environment to authenticate with. - */ - public AzureEnvironment getEnvironment() { - return environment; - } - @Override - public String getToken() throws IOException { - if (authenticationResult == null - || authenticationResult.getAccessToken() == null) { - acquireAccessToken(); + public String getToken(String resource) throws IOException { + AuthenticationResult authenticationResult = tokens.get(resource); + if (authenticationResult == null || authenticationResult.getExpiresOnDate().before(new Date())) { + authenticationResult = acquireAccessToken(resource); } return authenticationResult.getAccessToken(); } @Override - public void refreshToken() throws IOException { - acquireAccessToken(); + public AzureEnvironment getEnvironment() { + return this.environment; } - private void acquireAccessToken() throws IOException { + private AuthenticationResult acquireAccessToken(String resource) throws IOException { String authorityUrl = this.getEnvironment().getAuthenticationEndpoint() + this.getDomain(); ExecutorService executor = Executors.newSingleThreadExecutor(); AuthenticationContext context = new AuthenticationContext(authorityUrl, this.getEnvironment().isValidateAuthority(), executor); try { - authenticationResult = context.acquireToken( - this.resourceEndpoint, + AuthenticationResult result = context.acquireToken( + resource, new ClientCredential(this.getClientId(), this.getSecret()), null).get(); + tokens.put(resource, result); + return result; } catch (Exception e) { throw new IOException(e.getMessage(), e); } finally { executor.shutdown(); } } + + @Override + public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) { + clientBuilder.interceptors().add(new AzureTokenCredentialsInterceptor(this)); + } } diff --git a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java index c37479771a9b..9da95726b11a 100644 --- a/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java +++ b/azure-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java @@ -11,18 +11,21 @@ import com.microsoft.aad.adal4j.AuthenticationResult; import com.microsoft.azure.AzureEnvironment; import com.microsoft.rest.credentials.TokenCredentials; +import okhttp3.OkHttpClient; import java.io.IOException; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Token based credentials for use with a REST Service Client. */ -public class UserTokenCredentials extends TokenCredentials { - /** The endpoint of the target resource. */ - private String resourceEndpoint; +public class UserTokenCredentials extends TokenCredentials implements AzureTokenCredentials { + /** A mapping from resource endpoint to its cached access token. */ + private Map tokens; /** The Active Directory application client id. */ private String clientId; /** The domain or tenant id containing this application. */ @@ -31,12 +34,8 @@ public class UserTokenCredentials extends TokenCredentials { private String username; /** The password for the Organization Id account. */ private String password; - /** The Uri where the user will be redirected after authenticating with AD. */ - private String clientRedirectUri; /** The Azure environment to authenticate with. */ private AzureEnvironment environment; - /** The current authentication result. */ - private AuthenticationResult authenticationResult; /** * Initializes a new instance of the UserTokenCredentials. @@ -45,50 +44,17 @@ public class UserTokenCredentials extends TokenCredentials { * @param domain the domain or tenant id containing this application. * @param username the user name for the Organization Id account. * @param password the password for the Organization Id account. - * @param clientRedirectUri the Uri where the user will be redirected after authenticating with AD. * @param environment the Azure environment to authenticate with. * If null is provided, AzureEnvironment.AZURE will be used. */ - public UserTokenCredentials(String clientId, String domain, String username, String password, String clientRedirectUri, AzureEnvironment environment) { + public UserTokenCredentials(String clientId, String domain, String username, String password, AzureEnvironment environment) { super(null, null); // defer token acquisition this.clientId = clientId; this.domain = domain; this.username = username; this.password = password; - this.clientRedirectUri = clientRedirectUri; - if (environment == null) { - this.environment = AzureEnvironment.AZURE; - } else { - this.environment = environment; - } - this.resourceEndpoint = this.environment.getTokenAudience(); - } - - /** - * Initializes a new instance of the UserTokenCredentials. - * - * @param clientId the active directory application client id. - * @param domain the domain or tenant id containing this application. - * @param username the user name for the Organization Id account. - * @param password the password for the Organization Id account. - * @param clientRedirectUri the Uri where the user will be redirected after authenticating with AD. - * @param resourceEndpoint the endpoint of the target resource. - * @param environment the Azure environment to authenticate with. - * If null is provided, AzureEnvironment.AZURE will be used. - */ - public UserTokenCredentials(String clientId, String domain, String username, String password, String clientRedirectUri, String resourceEndpoint, AzureEnvironment environment) { - super(null, null); // defer token acquisition - this.clientId = clientId; - this.domain = domain; - this.username = username; - this.password = password; - this.clientRedirectUri = clientRedirectUri; - this.resourceEndpoint = resourceEndpoint; - if (environment == null) { - this.environment = AzureEnvironment.AZURE; - } else { - this.environment = environment; - } + this.environment = environment; + this.tokens = new HashMap<>(); } /** @@ -105,6 +71,7 @@ public String getClientId() { * * @return the tenant or domain the containing the application. */ + @Override public String getDomain() { return domain; } @@ -127,13 +94,13 @@ public String getPassword() { return password; } - /** - * Gets the Uri where the user will be redirected after authenticating with AD. - * - * @return the redirecting Uri. - */ - public String getClientRedirectUri() { - return clientRedirectUri; + @Override + public String getToken(String resource) throws IOException { + AuthenticationResult authenticationResult = tokens.get(resource); + if (authenticationResult == null || authenticationResult.getExpiresOnDate().before(new Date())) { + authenticationResult = acquireAccessToken(resource); + } + return authenticationResult.getAccessToken(); } /** @@ -145,50 +112,47 @@ public AzureEnvironment getEnvironment() { return environment; } - @Override - public String getToken() throws IOException { - if (authenticationResult != null - && authenticationResult.getExpiresOnDate().before(new Date())) { - acquireAccessTokenFromRefreshToken(); - } else { - acquireAccessToken(); - } - return authenticationResult.getAccessToken(); - } - - @Override - public void refreshToken() throws IOException { - acquireAccessToken(); - } - - private void acquireAccessToken() throws IOException { + private AuthenticationResult acquireAccessToken(String resource) throws IOException { String authorityUrl = this.getEnvironment().getAuthenticationEndpoint() + this.getDomain(); - AuthenticationContext context = new AuthenticationContext(authorityUrl, this.getEnvironment().isValidateAuthority(), Executors.newSingleThreadExecutor()); + ExecutorService executor = Executors.newSingleThreadExecutor(); + AuthenticationContext context = new AuthenticationContext(authorityUrl, this.getEnvironment().isValidateAuthority(), executor); try { - authenticationResult = context.acquireToken( - this.resourceEndpoint, + AuthenticationResult result = context.acquireToken( + resource, this.getClientId(), this.getUsername(), this.getPassword(), null).get(); + tokens.put(resource, result); + return result; } catch (Exception e) { throw new IOException(e.getMessage(), e); + } finally { + executor.shutdown(); } } - private void acquireAccessTokenFromRefreshToken() throws IOException { + // Refresh tokens are currently not used since we don't know if the refresh token has expired + private AuthenticationResult acquireAccessTokenFromRefreshToken(String resource) throws IOException { String authorityUrl = this.getEnvironment().getAuthenticationEndpoint() + this.getDomain(); ExecutorService executor = Executors.newSingleThreadExecutor(); AuthenticationContext context = new AuthenticationContext(authorityUrl, this.getEnvironment().isValidateAuthority(), executor); try { - authenticationResult = context.acquireTokenByRefreshToken( - authenticationResult.getRefreshToken(), + AuthenticationResult result = context.acquireTokenByRefreshToken( + tokens.get(resource).getRefreshToken(), this.getClientId(), null, null).get(); + tokens.put(resource, result); + return result; } catch (Exception e) { throw new IOException(e.getMessage(), e); } finally { executor.shutdown(); } } + + @Override + public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) { + clientBuilder.interceptors().add(new AzureTokenCredentialsInterceptor(this)); + } } diff --git a/azure-client-authentication/src/test/java/com/microsoft/azure/credentials/UserTokenCredentialsTests.java b/azure-client-authentication/src/test/java/com/microsoft/azure/credentials/UserTokenCredentialsTests.java index 7905ba7e7b21..caab41dd8e7c 100644 --- a/azure-client-authentication/src/test/java/com/microsoft/azure/credentials/UserTokenCredentialsTests.java +++ b/azure-client-authentication/src/test/java/com/microsoft/azure/credentials/UserTokenCredentialsTests.java @@ -21,7 +21,6 @@ public class UserTokenCredentialsTests { "domain", "username", "password", - "redirect", AzureEnvironment.AZURE ); @@ -36,8 +35,8 @@ public void testAcquireToken() throws Exception { public static class MockUserTokenCredentials extends UserTokenCredentials { private AuthenticationResult authenticationResult; - public MockUserTokenCredentials(String clientId, String domain, String username, String password, String clientRedirectUri, AzureEnvironment environment) { - super(clientId, domain, username, password, clientRedirectUri, environment); + public MockUserTokenCredentials(String clientId, String domain, String username, String password, AzureEnvironment environment) { + super(clientId, domain, username, password, environment); } @Override diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java index 897e29b622c0..421bc2869d0f 100644 --- a/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/AzureEnvironment.java @@ -14,17 +14,22 @@ public final class AzureEnvironment { /** * Base URL for calls to Azure management API. */ - private final String baseURL; + private final String resourceManagerEndpoint; /** - * ActiveDirectory Endpoint for the Azure Environment. + * ActiveDirectory Endpoint for the authentications. */ private String authenticationEndpoint; /** - * Token audience for an endpoint. + * Base URL for calls to service management and authentications to Active Directory. */ - private String tokenAudience; + private String managementEndpoint; + + /** + * Base URL for calls to graph API. + */ + private String graphEndpoint; /** * Determines whether the authentication endpoint should @@ -36,20 +41,20 @@ public final class AzureEnvironment { * Initializes an instance of AzureEnvironment class. * * @param authenticationEndpoint ActiveDirectory Endpoint for the Azure Environment. - * @param tokenAudience token audience for an endpoint. - * @param validateAuthority whether the authentication endpoint should - * be validated with Azure AD. - * @param baseUrl the base URL for the current environment. + * @param managementEndpoint token audience for an endpoint. + * @param resourceManagerEndpoint the base URL for the current environment. + * @param graphEndpoint the base URL for graph API. */ public AzureEnvironment( String authenticationEndpoint, - String tokenAudience, - boolean validateAuthority, - String baseUrl) { + String managementEndpoint, + String resourceManagerEndpoint, + String graphEndpoint) { this.authenticationEndpoint = authenticationEndpoint; - this.tokenAudience = tokenAudience; - this.validateAuthority = validateAuthority; - this.baseURL = baseUrl; + this.managementEndpoint = managementEndpoint; + this.resourceManagerEndpoint = resourceManagerEndpoint; + this.graphEndpoint = graphEndpoint; + this.validateAuthority = false; } /** @@ -58,8 +63,8 @@ public AzureEnvironment( public static final AzureEnvironment AZURE = new AzureEnvironment( "https://login.microsoftonline.com/", "https://management.core.windows.net/", - true, - "https://management.azure.com/"); + "https://management.azure.com/", + "https://graph.windows.net/"); /** * Provides the settings for authentication with Azure China. @@ -67,8 +72,8 @@ public AzureEnvironment( public static final AzureEnvironment AZURE_CHINA = new AzureEnvironment( "https://login.chinacloudapi.cn/", "https://management.core.chinacloudapi.cn/", - true, - "https://management.chinacloudapi.cn/"); + "https://management.chinacloudapi.cn/", + "https://graph.chinacloudapi.cn/"); /** * Provides the settings for authentication with Azure US Government. @@ -76,8 +81,8 @@ public AzureEnvironment( public static final AzureEnvironment AZURE_US_GOVERNMENT = new AzureEnvironment( "https://login.microsoftonline.com/", "https://management.core.usgovcloudapi.net/", - true, - "https://management.usgovcloudapi.net/"); + "https://management.usgovcloudapi.net/", + "https://graph.windows.net/"); /** * Provides the settings for authentication with Azure Germany. @@ -85,8 +90,8 @@ public AzureEnvironment( public static final AzureEnvironment AZURE_GERMANY = new AzureEnvironment( "https://login.microsoftonline.de/", "https://management.core.cloudapi.de/", - true, - "https://management.microsoftazure.de/"); + "https://management.microsoftazure.de/", + "https://graph.cloudapi.de/"); /** * Gets the base URL of the management service. @@ -94,12 +99,10 @@ public AzureEnvironment( * @return the Base URL for the management service. */ public String getBaseUrl() { - return this.baseURL; + return this.resourceManagerEndpoint; } /** - * Gets a builder for {@link RestClient}. - * * @return a builder for the rest client. */ public RestClient.Builder.Buildable newRestClientBuilder() { @@ -109,8 +112,6 @@ public RestClient.Builder.Buildable newRestClientBuilder() { } /** - * Gets the ActiveDirectory Endpoint for the Azure Environment. - * * @return the ActiveDirectory Endpoint for the Azure Environment. */ public String getAuthenticationEndpoint() { @@ -118,12 +119,17 @@ public String getAuthenticationEndpoint() { } /** - * Gets the token audience for an endpoint. - * - * @return the token audience for an endpoint. + * @return the Azure Resource Manager endpoint for the environment. + */ + public String getManagementEndpoint() { + return managementEndpoint; + } + + /** + * @return the Graph API endpoint. */ - public String getTokenAudience() { - return tokenAudience; + public String getGraphEndpoint() { + return graphEndpoint; } /** @@ -136,4 +142,15 @@ public String getTokenAudience() { public boolean isValidateAuthority() { return validateAuthority; } + + /** + * Sets whether the authentication endpoint should + * be validated with Azure AD. + * + * @param validateAuthority true if the authentication endpoint should + * be validated with Azure AD, false otherwise. + */ + public void setValidateAuthority(boolean validateAuthority) { + this.validateAuthority = validateAuthority; + } } diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/PagedList.java b/azure-client-runtime/src/main/java/com/microsoft/azure/PagedList.java index 8db10337d989..46c02402ed53 100644 --- a/azure-client-runtime/src/main/java/com/microsoft/azure/PagedList.java +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/PagedList.java @@ -17,8 +17,6 @@ import java.util.ListIterator; import java.util.NoSuchElementException; -import javax.xml.bind.DataBindingException; - /** * Defines a list response from a paging operation. The pages are * lazy initialized when an instance of this class is iterated. @@ -28,10 +26,10 @@ public abstract class PagedList implements List { /** The actual items in the list. */ private List items; - /** Stores the link to get the next page of items. */ - private String nextPageLink; /** Stores the latest page fetched. */ private Page currentPage; + /** Cached page right after the current one. */ + private Page cachedPage; /** * Creates an instance of Pagedlist. @@ -48,11 +46,26 @@ public PagedList() { public PagedList(Page page) { this(); List retrievedItems = page.getItems(); - if (retrievedItems != null && retrievedItems.size() != 0) { + if (retrievedItems != null) { items.addAll(retrievedItems); } - nextPageLink = page.getNextPageLink(); currentPage = page; + cachePage(page.getNextPageLink()); + } + + private void cachePage(String nextPageLink) { + try { + while (nextPageLink != null) { + cachedPage = nextPage(nextPageLink); + nextPageLink = cachedPage.getNextPageLink(); + if (hasNextPage()) { + // a legit, non-empty page has been fetched, otherwise keep fetching + break; + } + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } } /** @@ -71,7 +84,7 @@ public PagedList(Page page) { * @return true if there are more pages to load. False otherwise. */ public boolean hasNextPage() { - return this.nextPageLink != null; + return this.cachedPage != null && this.cachedPage.getItems() != null && !this.cachedPage.getItems().isEmpty(); } /** @@ -79,14 +92,10 @@ public boolean hasNextPage() { * The exceptions are wrapped into Java Runtime exceptions. */ public void loadNextPage() { - try { - Page nextPage = nextPage(this.nextPageLink); - this.nextPageLink = nextPage.getNextPageLink(); - this.items.addAll(nextPage.getItems()); - this.currentPage = nextPage; - } catch (IOException e) { - throw new DataBindingException(e.getMessage(), e); - } + this.currentPage = cachedPage; + cachedPage = null; + this.items.addAll(currentPage.getItems()); + cachePage(currentPage.getNextPageLink()); } /** @@ -108,12 +117,17 @@ public Page currentPage() { } /** - * Gets the next page's link. + * Sets the current page. * - * @return the next page link. + * @param currentPage the current page. */ - public String nextPageLink() { - return nextPageLink; + protected void setCurrentPage(Page currentPage) { + this.currentPage = currentPage; + List retrievedItems = currentPage.getItems(); + if (retrievedItems != null) { + items.addAll(retrievedItems); + } + cachePage(currentPage.getNextPageLink()); } /** @@ -141,17 +155,14 @@ public boolean hasNext() { public E next() { if (!itemsListItr.hasNext()) { if (!hasNextPage()) { - throw new NoSuchElementException(); + throw new NoSuchElementException(); } else { int size = items.size(); loadNextPage(); itemsListItr = items.listIterator(size); } } - if (itemsListItr.hasNext()) { - return itemsListItr.next(); - } - return null; + return itemsListItr.next(); } @Override diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/credentials/AzureTokenCredentials.java b/azure-client-runtime/src/main/java/com/microsoft/azure/credentials/AzureTokenCredentials.java new file mode 100644 index 000000000000..d91962e79855 --- /dev/null +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/credentials/AzureTokenCredentials.java @@ -0,0 +1,40 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.credentials; + +import com.microsoft.azure.AzureEnvironment; +import com.microsoft.rest.credentials.ServiceClientCredentials; + +import java.io.IOException; + +/** + * AzureTokenCredentials represents a credentials object with access to Azure + * Resource management. + */ +public interface AzureTokenCredentials extends ServiceClientCredentials { + /** + * Override this method to provide the mechanism to get a token. + * + * @param resource the resource the access token is for + * @return the token to access the resource + * @throws IOException exceptions from IO + */ + String getToken(String resource) throws IOException; + + /** + * Override this method to provide the domain or tenant ID the token is valid in. + * + * @return the domain or tenant ID string + */ + String getDomain(); + + /** + * @return the environment details the credential has access to. + */ + AzureEnvironment getEnvironment(); +} diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/credentials/AzureTokenCredentialsInterceptor.java b/azure-client-runtime/src/main/java/com/microsoft/azure/credentials/AzureTokenCredentialsInterceptor.java new file mode 100644 index 000000000000..33f8b7c7a16d --- /dev/null +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/credentials/AzureTokenCredentialsInterceptor.java @@ -0,0 +1,59 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.credentials; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; + +/** + * Token credentials filter for placing a token credential into request headers. + */ +public class AzureTokenCredentialsInterceptor implements Interceptor { + /** + * The credentials instance to apply to the HTTP client pipeline. + */ + private AzureTokenCredentials credentials; + + /** + * Initialize a TokenCredentialsFilter class with a + * TokenCredentials credential. + * + * @param credentials a TokenCredentials instance + */ + public AzureTokenCredentialsInterceptor(AzureTokenCredentials credentials) { + this.credentials = credentials; + } + + @Override + public Response intercept(Chain chain) throws IOException { + String resource = credentials.getEnvironment().getManagementEndpoint(); + // Use graph resource if the host if graph endpoint + if (credentials.getEnvironment().getGraphEndpoint().contains(chain.request().url().host())) { + resource = credentials.getEnvironment().getGraphEndpoint(); + } + Request newRequest = chain.request().newBuilder() + .header("Authorization", "Bearer " + credentials.getToken(resource)) + .build(); + return chain.proceed(newRequest); + } + + private Response sendRequestWithToken(Chain chain) throws IOException { + String resource = credentials.getEnvironment().getManagementEndpoint(); + // Use graph resource if the host if graph endpoint + if (chain.request().url().host().equals(credentials.getEnvironment().getGraphEndpoint())) { + resource = credentials.getEnvironment().getGraphEndpoint(); + } + Request newRequest = chain.request().newBuilder() + .header("Authorization", "Bearer " + credentials.getToken(resource)) + .build(); + return chain.proceed(newRequest); + } +} diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/credentials/package-info.java b/azure-client-runtime/src/main/java/com/microsoft/azure/credentials/package-info.java new file mode 100644 index 000000000000..22ff7d1a5d6e --- /dev/null +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/credentials/package-info.java @@ -0,0 +1,6 @@ +/** + * The package contains the credentials classes required for AutoRest generated + * Azure clients to compile and function. To learn more about AutoRest generator, + * see https://github.com/azure/autorest. + */ +package com.microsoft.azure.credentials; \ No newline at end of file diff --git a/azure-client-runtime/src/test/java/com/microsoft/azure/PagedListTests.java b/azure-client-runtime/src/test/java/com/microsoft/azure/PagedListTests.java index b82c8d062878..dfac45400504 100644 --- a/azure-client-runtime/src/test/java/com/microsoft/azure/PagedListTests.java +++ b/azure-client-runtime/src/test/java/com/microsoft/azure/PagedListTests.java @@ -11,7 +11,6 @@ import org.junit.Before; import org.junit.Test; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -21,11 +20,11 @@ public class PagedListTests { @Before public void setupList() { - list = new PagedList(new TestPage(0, 20)) { + list = new PagedList(new TestPage(0, 21)) { @Override - public Page nextPage(String nextPageLink) throws CloudException, IOException { + public Page nextPage(String nextPageLink) { int pageNum = Integer.parseInt(nextPageLink); - return new TestPage(pageNum, 20); + return new TestPage(pageNum, 21); } }; } @@ -119,9 +118,13 @@ public String getNextPageLink() { @Override public List getItems() { - List items = new ArrayList<>(); - items.add(page); - return items; + if (page + 1 != max) { + List items = new ArrayList<>(); + items.add(page); + return items; + } else { + return new ArrayList<>(); + } } } } diff --git a/client-runtime/src/main/java/com/microsoft/rest/ServiceResponseBuilder.java b/client-runtime/src/main/java/com/microsoft/rest/ServiceResponseBuilder.java index 3ad57cb938f4..3e16e187eeaf 100644 --- a/client-runtime/src/main/java/com/microsoft/rest/ServiceResponseBuilder.java +++ b/client-runtime/src/main/java/com/microsoft/rest/ServiceResponseBuilder.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.microsoft.rest.serializer.JacksonMapperAdapter; +import okhttp3.ResponseBody; +import retrofit2.Response; import java.io.IOException; import java.io.InputStream; @@ -18,9 +20,6 @@ import java.util.HashMap; import java.util.Map; -import okhttp3.ResponseBody; -import retrofit2.Response; - /** * The builder for building a {@link ServiceResponse}. * @@ -144,7 +143,9 @@ public ServiceResponse build(Response response) throws E, IOExc return new ServiceResponse<>((T) buildBody(statusCode, responseBody), response); } else { try { - E exception = (E) exceptionType.getConstructor(String.class).newInstance("Invalid status code " + statusCode); + String responseContent = responseBody.string(); + responseBody = ResponseBody.create(responseBody.contentType(), responseContent); + E exception = (E) exceptionType.getConstructor(String.class).newInstance(responseContent); exceptionType.getMethod("setResponse", response.getClass()).invoke(exception, response); exceptionType.getMethod("setBody", (Class) responseTypes.get(0)).invoke(exception, buildBody(statusCode, responseBody)); throw exception; diff --git a/client-runtime/src/main/java/com/microsoft/rest/Validator.java b/client-runtime/src/main/java/com/microsoft/rest/Validator.java index 51c24722404a..64e3436b0b63 100644 --- a/client-runtime/src/main/java/com/microsoft/rest/Validator.java +++ b/client-runtime/src/main/java/com/microsoft/rest/Validator.java @@ -16,6 +16,7 @@ import org.joda.time.Period; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.List; import java.util.Map; @@ -63,6 +64,11 @@ public static void validate(Object parameter) throws IllegalArgumentException { } for (Field field : c.getDeclaredFields()) { field.setAccessible(true); + int mod = field.getModifiers(); + // Skip static fields since we don't have any, skip final fields since users can't modify them + if (Modifier.isFinal(mod) || Modifier.isStatic(mod)) { + return; + } JsonProperty annotation = field.getAnnotation(JsonProperty.class); Object property; try { diff --git a/client-runtime/src/main/java/com/microsoft/rest/serializer/FlatteningDeserializer.java b/client-runtime/src/main/java/com/microsoft/rest/serializer/FlatteningDeserializer.java index dd7468a72837..57dcfa94d55c 100644 --- a/client-runtime/src/main/java/com/microsoft/rest/serializer/FlatteningDeserializer.java +++ b/client-runtime/src/main/java/com/microsoft/rest/serializer/FlatteningDeserializer.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.reflect.TypeToken; import java.io.IOException; import java.lang.reflect.Field; @@ -80,21 +81,27 @@ public JsonDeserializer modifyDeserializer(DeserializationConfig config, Bean public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { JsonNode root = mapper.readTree(jp); final Class tClass = this.defaultDeserializer.handledType(); - for (Field field : tClass.getDeclaredFields()) { - JsonNode node = root; - JsonProperty property = field.getAnnotation(JsonProperty.class); - if (property != null) { - String value = property.value(); - if (value.matches(".+[^\\\\]\\..+")) { - String[] values = value.split("((? c : TypeToken.of(tClass).getTypes().classes().rawTypes()) { + // Ignore checks for Object type. + if (c.isAssignableFrom(Object.class)) { + continue; + } + for (Field field : c.getDeclaredFields()) { + JsonNode node = root; + JsonProperty property = field.getAnnotation(JsonProperty.class); + if (property != null) { + String value = property.value(); + if (value.matches(".+[^\\\\]\\..+")) { + String[] values = value.split("((?