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 abe3a65c9bdd8..dc5ef9dc7d3b4 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<>(); } /** @@ -101,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; @@ -155,7 +132,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); @@ -168,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( @@ -177,8 +155,8 @@ public static ApplicationTokenCredentials fromFile(File credentialsFile) throws new AzureEnvironment( authUrl, mgmtUri, - true, - baseUrl) + baseUrl, + graphUrl) ).withDefaultSubscriptionId(defaultSubscriptionId); } @@ -196,6 +174,7 @@ public String getClientId() { * * @return the tenant or domain the containing the application. */ + @Override public String getDomain() { return domain; } @@ -209,42 +188,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 c37479771a9b9..9da95726b11a2 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 7905ba7e7b21e..caab41dd8e7c2 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 897e29b622c09..421bc2869d0f4 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 8db10337d9898..46c02402ed531 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 0000000000000..d91962e798554 --- /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 0000000000000..33f8b7c7a16d0 --- /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 0000000000000..22ff7d1a5d6ed --- /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 b82c8d0628787..dfac45400504b 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 3ad57cb938f40..3e16e187eeaf3 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 51c24722404af..64e3436b0b633 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 dd7468a728372..57dcfa94d55cf 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("((?