From ba07dcb31ac87d91e9dd0e8194b25979ff7ffc29 Mon Sep 17 00:00:00 2001 From: Yaohai Zheng Date: Mon, 4 Nov 2019 10:12:57 +0800 Subject: [PATCH 1/3] Add auxiliary authorization support for Azure cross tenant. --- .../fluentcore/arm/AzureConfigurable.java | 9 ++++ .../implementation/AzureConfigurableImpl.java | 14 +++++ .../utils/AuxiliaryTokensInterceptor.java | 54 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryTokensInterceptor.java 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..23eb4aabcfd 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 tokens for Azure which can hold up to three. + * + * @param tokens the AzureTokenCredentials list + * @return the configurable object itself + */ + T withAuxiliaryTokens(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..72664a6f7cc 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.AuxiliaryTokensInterceptor; 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 withAuxiliaryTokens(AzureTokenCredentials... tokens) { + if (tokens != null) { + if (tokens.length > 3) { + throw new IllegalArgumentException("Only can hold up to three auxiliary tokens."); + } + AuxiliaryTokensInterceptor interceptor = new AuxiliaryTokensInterceptor(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/AuxiliaryTokensInterceptor.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryTokensInterceptor.java new file mode 100644 index 00000000000..e5dd4ed94e3 --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryTokensInterceptor.java @@ -0,0 +1,54 @@ +/** + * 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.credentials.AzureTokenCredentials; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; + +/** + * An interceptor for cross-tenant authorization in Azure. + */ +public final class AuxiliaryTokensInterceptor implements Interceptor { + + private static final String AUTHORIZATION_AUXILIARY_HEADER = "x-ms-authorization-auxiliary"; + private static final String SCHEMA = "Bearer"; + + private final AzureTokenCredentials[] tokens; + + /** + * Initialize an auxiliary interceptor with the list of AzureTokenCredentials. + * + * @param tokens the AzureTokenCredentials list + */ + public AuxiliaryTokensInterceptor(AzureTokenCredentials... tokens) { + this.tokens = tokens; + } + + @Override + public Response intercept(Chain chain) throws IOException { + if (this.tokens == null || this.tokens.length == 0) { + return chain.proceed(chain.request()); + } + StringBuffer buff = new StringBuffer(); + for (int i = 0; i < tokens.length; i++) { + buff.append(SCHEMA); + buff.append(" "); + buff.append(tokens[i].getToken(chain.request().url().scheme() + "://" + chain.request().url().host())); + if (i < tokens.length - 1) { + buff.append(";"); + } + } + Request request = chain.request().newBuilder() + .header(AUTHORIZATION_AUXILIARY_HEADER, buff.toString()) + .build(); + return chain.proceed(request); + } +} From 056d57eb68e176c6a247944145d993a48a4ded59 Mon Sep 17 00:00:00 2001 From: Yaohai Zheng Date: Tue, 5 Nov 2019 14:52:22 +0800 Subject: [PATCH 2/3] Resolve code review feedback. --- .../fluentcore/arm/AzureConfigurable.java | 4 +- .../implementation/AzureConfigurableImpl.java | 6 +- .../AuxiliaryCredentialsInterceptor.java | 77 +++++++++++++++++++ .../utils/AuxiliaryTokensInterceptor.java | 54 ------------- .../ProviderRegistrationInterceptor.java | 12 +-- .../resources/fluentcore/utils/Utils.java | 21 +++++ 6 files changed, 104 insertions(+), 70 deletions(-) create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryCredentialsInterceptor.java delete mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryTokensInterceptor.java 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 23eb4aabcfd..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 @@ -40,12 +40,12 @@ public interface AzureConfigurable> { T withInterceptor(Interceptor interceptor); /** - * Set the cross-tenant auxiliary tokens for Azure which can hold up to three. + * 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 withAuxiliaryTokens(AzureTokenCredentials... tokens); + 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 72664a6f7cc..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,7 +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.AuxiliaryTokensInterceptor; +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; @@ -57,12 +57,12 @@ public T withInterceptor(Interceptor interceptor) { @SuppressWarnings("unchecked") @Override - public T withAuxiliaryTokens(AzureTokenCredentials... tokens) { + public T withAuxiliaryCredentials(AzureTokenCredentials... tokens) { if (tokens != null) { if (tokens.length > 3) { throw new IllegalArgumentException("Only can hold up to three auxiliary tokens."); } - AuxiliaryTokensInterceptor interceptor = new AuxiliaryTokensInterceptor(tokens); + AuxiliaryCredentialsInterceptor interceptor = new AuxiliaryCredentialsInterceptor(tokens); this.restClientBuilder = this.restClientBuilder.withInterceptor(interceptor); } return (T) this; 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/AuxiliaryTokensInterceptor.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryTokensInterceptor.java deleted file mode 100644 index e5dd4ed94e3..00000000000 --- a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/utils/AuxiliaryTokensInterceptor.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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.credentials.AzureTokenCredentials; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -import java.io.IOException; - -/** - * An interceptor for cross-tenant authorization in Azure. - */ -public final class AuxiliaryTokensInterceptor implements Interceptor { - - private static final String AUTHORIZATION_AUXILIARY_HEADER = "x-ms-authorization-auxiliary"; - private static final String SCHEMA = "Bearer"; - - private final AzureTokenCredentials[] tokens; - - /** - * Initialize an auxiliary interceptor with the list of AzureTokenCredentials. - * - * @param tokens the AzureTokenCredentials list - */ - public AuxiliaryTokensInterceptor(AzureTokenCredentials... tokens) { - this.tokens = tokens; - } - - @Override - public Response intercept(Chain chain) throws IOException { - if (this.tokens == null || this.tokens.length == 0) { - return chain.proceed(chain.request()); - } - StringBuffer buff = new StringBuffer(); - for (int i = 0; i < tokens.length; i++) { - buff.append(SCHEMA); - buff.append(" "); - buff.append(tokens[i].getToken(chain.request().url().scheme() + "://" + chain.request().url().host())); - if (i < tokens.length - 1) { - buff.append(";"); - } - } - Request request = chain.request().newBuilder() - .header(AUTHORIZATION_AUXILIARY_HEADER, buff.toString()) - .build(); - return chain.proceed(request); - } -} 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..35c452b268a 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 @@ -42,7 +42,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 +76,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..8532a09e145 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 @@ -8,14 +8,19 @@ import com.google.common.primitives.Ints; import com.microsoft.azure.AzureEnvironment; +import com.microsoft.azure.CloudError; import com.microsoft.azure.Page; import com.microsoft.azure.PagedList; import com.microsoft.azure.credentials.AzureTokenCredentials; import com.microsoft.azure.management.resources.fluentcore.arm.ResourceId; import com.microsoft.azure.management.resources.fluentcore.model.Indexable; import com.microsoft.azure.management.resources.implementation.PageImpl; +import com.microsoft.azure.serializer.AzureJacksonAdapter; import com.microsoft.rest.RestClient; +import okhttp3.Response; import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; import retrofit2.Retrofit; import retrofit2.http.GET; import retrofit2.http.Url; @@ -252,6 +257,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 + */ + 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() { } } From 57de4deda0f7c608665fd790400ed888e92a66cc Mon Sep 17 00:00:00 2001 From: Yaohai Zheng Date: Wed, 6 Nov 2019 12:46:41 +0800 Subject: [PATCH 3/3] Make checksytle happy. --- .../fluentcore/utils/ProviderRegistrationInterceptor.java | 3 --- .../azure/management/resources/fluentcore/utils/Utils.java | 7 ++----- 2 files changed, 2 insertions(+), 8 deletions(-) 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 35c452b268a..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; 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 8532a09e145..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 @@ -8,16 +8,13 @@ import com.google.common.primitives.Ints; import com.microsoft.azure.AzureEnvironment; -import com.microsoft.azure.CloudError; import com.microsoft.azure.Page; import com.microsoft.azure.PagedList; import com.microsoft.azure.credentials.AzureTokenCredentials; import com.microsoft.azure.management.resources.fluentcore.arm.ResourceId; import com.microsoft.azure.management.resources.fluentcore.model.Indexable; import com.microsoft.azure.management.resources.implementation.PageImpl; -import com.microsoft.azure.serializer.AzureJacksonAdapter; import com.microsoft.rest.RestClient; -import okhttp3.Response; import okhttp3.ResponseBody; import okio.Buffer; import okio.BufferedSource; @@ -258,10 +255,10 @@ public static String resourceGroupId(String id) { } /** - * Get the response body as String + * Get the response body as string. * @param responseBody response body object * @return response body in string - * @throws IOException + * @throws IOException throw IOException */ public static String getResponseBodyInString(ResponseBody responseBody) throws IOException { if (responseBody == null) {