diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/arm/AzureConfigurable.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/arm/AzureConfigurable.java index 6ff289c1763..aba0ad0293c 100644 --- a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/arm/AzureConfigurable.java +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/arm/AzureConfigurable.java @@ -6,6 +6,7 @@ package com.microsoft.azure.management.resources.fluentcore.arm; +import com.microsoft.azure.credentials.AzureTokenCredentials; import com.microsoft.rest.LogLevel; import okhttp3.Authenticator; import okhttp3.ConnectionPool; @@ -38,6 +39,14 @@ public interface AzureConfigurable> { */ T withInterceptor(Interceptor interceptor); + /** + * Set the cross-tenant auxiliary credentials for Azure which can hold up to three. + * + * @param tokens the AzureTokenCredentials list + * @return the configurable object itself + */ + T withAuxiliaryCredentials(AzureTokenCredentials... tokens); + /** * Specify the user agent header. * diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/arm/implementation/AzureConfigurableImpl.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/arm/implementation/AzureConfigurableImpl.java index c41ba1b3c9c..44a5cba733d 100644 --- a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/arm/implementation/AzureConfigurableImpl.java +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/arm/implementation/AzureConfigurableImpl.java @@ -10,6 +10,7 @@ import com.microsoft.azure.AzureResponseBuilder; import com.microsoft.azure.credentials.AzureTokenCredentials; import com.microsoft.azure.management.resources.fluentcore.arm.AzureConfigurable; +import com.microsoft.azure.management.resources.fluentcore.utils.AuxiliaryCredentialsInterceptor; import com.microsoft.azure.management.resources.fluentcore.utils.ProviderRegistrationInterceptor; import com.microsoft.azure.management.resources.fluentcore.utils.ResourceManagerThrottlingInterceptor; import com.microsoft.azure.serializer.AzureJacksonAdapter; @@ -54,6 +55,19 @@ public T withInterceptor(Interceptor interceptor) { return (T) this; } + @SuppressWarnings("unchecked") + @Override + public T withAuxiliaryCredentials(AzureTokenCredentials... tokens) { + if (tokens != null) { + if (tokens.length > 3) { + throw new IllegalArgumentException("Only can hold up to three auxiliary tokens."); + } + AuxiliaryCredentialsInterceptor interceptor = new AuxiliaryCredentialsInterceptor(tokens); + this.restClientBuilder = this.restClientBuilder.withInterceptor(interceptor); + } + return (T) this; + } + @SuppressWarnings("unchecked") @Override public T withUserAgent(String userAgent) { diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryCredentialsInterceptor.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryCredentialsInterceptor.java new file mode 100644 index 00000000000..07e6c4f192d --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryCredentialsInterceptor.java @@ -0,0 +1,77 @@ +/** + * 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.management.resources.fluentcore.utils; + +import com.microsoft.azure.CloudError; +import com.microsoft.azure.credentials.AzureTokenCredentials; +import com.microsoft.azure.serializer.AzureJacksonAdapter; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; + +import java.io.IOException; + +/** + * An interceptor for cross-tenant authorization in Azure. + */ +public final class AuxiliaryCredentialsInterceptor implements Interceptor { + + private static final String AUTHORIZATION_AUXILIARY_HEADER = "x-ms-authorization-auxiliary"; + private static final String LINKED_AUTHORIZATION_FAILED = "LinkedAuthorizationFailed"; + private static final String SCHEMA = "Bearer"; + + private final AzureTokenCredentials[] tokenCredentials; + + /** + * Initialize an auxiliary interceptor with the list of AzureTokenCredentials. + * + * @param credentials the AzureTokenCredentials list + */ + public AuxiliaryCredentialsInterceptor(AzureTokenCredentials... credentials) { + this.tokenCredentials = credentials; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + if (!response.isSuccessful() && tokenCredentials != null && this.tokenCredentials.length > 0) { + String content = errorBody(response.body()); + AzureJacksonAdapter jacksonAdapter = new AzureJacksonAdapter(); + CloudError cloudError = jacksonAdapter.deserialize(content, CloudError.class); + if (cloudError != null && LINKED_AUTHORIZATION_FAILED.equals(cloudError.code())) { + StringBuffer buff = new StringBuffer(); + for (int i = 0; i < tokenCredentials.length; i++) { + buff.append(SCHEMA); + buff.append(" "); + buff.append(tokenCredentials[i].getToken(chain.request().url().scheme() + "://" + chain.request().url().host())); + if (i < tokenCredentials.length - 1) { + buff.append(";"); + } + } + Request request = chain.request().newBuilder() + .header(AUTHORIZATION_AUXILIARY_HEADER, buff.toString()) + .build(); + // Retry + return chain.proceed(request); + } + } + return response; + } + + private String errorBody(ResponseBody responseBody) throws IOException { + if (responseBody == null) { + return null; + } + BufferedSource source = responseBody.source(); + source.request(Long.MAX_VALUE); // Buffer the entire body. + Buffer buffer = source.buffer(); + return buffer.clone().readUtf8(); + } +} diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/ProviderRegistrationInterceptor.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/ProviderRegistrationInterceptor.java index 75b9892842d..e033d73df56 100644 --- a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/ProviderRegistrationInterceptor.java +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/ProviderRegistrationInterceptor.java @@ -15,9 +15,6 @@ import com.microsoft.rest.RestClient; import okhttp3.Interceptor; import okhttp3.Response; -import okhttp3.ResponseBody; -import okio.Buffer; -import okio.BufferedSource; import java.io.IOException; import java.util.regex.Matcher; @@ -42,7 +39,7 @@ public ProviderRegistrationInterceptor(AzureTokenCredentials credentials) { public Response intercept(Chain chain) throws IOException { Response response = chain.proceed(chain.request()); if (!response.isSuccessful()) { - String content = errorBody(response.body()); + String content = Utils.getResponseBodyInString(response.body()); AzureJacksonAdapter jacksonAdapter = new AzureJacksonAdapter(); CloudError cloudError = jacksonAdapter.deserialize(content, CloudError.class); if (cloudError != null && "MissingSubscriptionRegistration".equals(cloudError.code())) { @@ -76,16 +73,6 @@ public Response intercept(Chain chain) throws IOException { return response; } - private String errorBody(ResponseBody responseBody) throws IOException { - if (responseBody == null) { - return null; - } - BufferedSource source = responseBody.source(); - source.request(Long.MAX_VALUE); // Buffer the entire body. - Buffer buffer = source.buffer(); - return buffer.clone().readUtf8(); - } - private Provider registerProvider(String namespace, ResourceManager resourceManager) { return resourceManager.providers().register(namespace); } diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/Utils.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/Utils.java index aa687c450a4..e7558671bc0 100644 --- a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/Utils.java +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/Utils.java @@ -16,6 +16,8 @@ import com.microsoft.azure.management.resources.implementation.PageImpl; import com.microsoft.rest.RestClient; import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; import retrofit2.Retrofit; import retrofit2.http.GET; import retrofit2.http.Url; @@ -252,6 +254,22 @@ public static String resourceGroupId(String id) { resourceId.resourceGroupName()); } + /** + * Get the response body as string. + * @param responseBody response body object + * @return response body in string + * @throws IOException throw IOException + */ + public static String getResponseBodyInString(ResponseBody responseBody) throws IOException { + if (responseBody == null) { + return null; + } + BufferedSource source = responseBody.source(); + source.request(Long.MAX_VALUE); // Buffer the entire body. + Buffer buffer = source.buffer(); + return buffer.clone().readUtf8(); + } + private Utils() { } }