This is an automated email from the ASF dual-hosted git repository. gaul pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/jclouds.git
The following commit(s) were added to refs/heads/master by this push: new 0b68e8a JCLOUDS-1558: Implement Azure Blob Azure AD auth 0b68e8a is described below commit 0b68e8adee5e370184311c3fe4931ed8416d12d4 Author: Timur Alperovich <ti...@timuralp.com> AuthorDate: Mon Aug 9 23:46:24 2021 -0700 JCLOUDS-1558: Implement Azure Blob Azure AD auth Implements the Azure AD authentication for Azure Blob, using the OAuth module. Added more parameters to the AzureBlob provider: - azureblob.auth - azureblob.account - azureblob.tenantId The "auth" parameter is used to specify whether Key/SAS auth or Active Directory is used. When using Active Directory auth, the identity no longer maps to the storage account, which has to be specified explicitly. The tenant ID also needs to be supplied to construct the auth URL to obtain the token correctly. --- providers/azureblob/pom.xml | 6 ++ .../org/jclouds/azure/storage/config/AuthType.java | 37 ++++++++++++ .../config/AzureStorageOAuthConfigFactory.java | 59 +++++++++++++++++++ .../storage/config/AzureStorageProperties.java | 27 +++++++++ .../filters/SharedKeyLiteAuthentication.java | 19 ++++-- .../util/storageurl/StorageAccountInVhost.java | 19 +++++- .../jclouds/azureblob/AzureBlobApiMetadata.java | 13 ++++- .../azureblob/AzureBlobProviderMetadata.java | 15 ++++- .../binders/BindAzureBlobMetadataToRequest.java | 2 +- .../blobstore/AzureBlobRequestSigner.java | 25 +++++++- .../config/AzureBlobStoreContextModule.java | 5 +- .../azureblob/config/AzureBlobHttpApiModule.java | 9 ++- .../jclouds/azureblob/config/AzureBlobModule.java | 9 +++ .../util/storageurl/StorageAccountInVhostTest.java | 13 ++++- .../jclouds/azureblob/AzureBlobClientAdTest.java | 67 ++++++++++++++++++++++ .../jclouds/azureblob/AzureBlobClientLiveTest.java | 18 +++--- 16 files changed, 315 insertions(+), 28 deletions(-) diff --git a/providers/azureblob/pom.xml b/providers/azureblob/pom.xml index 62cfd6e..d9a6451 100644 --- a/providers/azureblob/pom.xml +++ b/providers/azureblob/pom.xml @@ -70,6 +70,12 @@ <artifactId>auto-service</artifactId> <optional>true</optional> </dependency> + <dependency> + <groupId>org.apache.jclouds.api</groupId> + <artifactId>oauth</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + </dependency> </dependencies> <profiles> diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AuthType.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AuthType.java new file mode 100644 index 0000000..986f21d --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AuthType.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.azure.storage.config; + +import static com.google.common.base.CaseFormat.LOWER_CAMEL; +import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; +import static com.google.common.base.Preconditions.checkNotNull; + +public enum AuthType { + /** Includes both the API key and SAS credentials */ + AZURE_KEY, + /** Azure AD credentials */ + AZURE_AD; + + @Override + public String toString() { + return UPPER_UNDERSCORE.to(LOWER_CAMEL, name()); + } + + public static AuthType fromValue(String authType) { + return valueOf(LOWER_CAMEL.to(UPPER_UNDERSCORE, checkNotNull(authType, "authType"))); + } +} diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AzureStorageOAuthConfigFactory.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AzureStorageOAuthConfigFactory.java new file mode 100644 index 0000000..c64ac72 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AzureStorageOAuthConfigFactory.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.azure.storage.config; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import org.jclouds.http.HttpRequest; +import org.jclouds.oauth.v2.config.OAuthConfigFactory; +import org.jclouds.oauth.v2.config.OAuthScopes; + +import static org.jclouds.azure.storage.config.AzureStorageProperties.ACCOUNT; +import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE; +import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE; + +public class AzureStorageOAuthConfigFactory implements OAuthConfigFactory { + private final OAuthScopes scopes; + + @Named(AUDIENCE) + @Inject(optional = true) + private String audience; + + @Named(RESOURCE) + @Inject(optional = true) + private String resource; + + @Named(ACCOUNT) + @Inject + private String account; + + @Inject + AzureStorageOAuthConfigFactory(OAuthScopes scopes) { this.scopes = scopes; } + + @Override + public OAuthConfig forRequest(HttpRequest input) { + String authResource = resource; + if (authResource == null) { + authResource = "https://" + account + ".blob.core.windows.net"; + } + String authAudience = audience; + if (authAudience == null) { + authAudience = "https://" + account + ".blob.core.windows.net"; + } + return OAuthConfig.create(scopes.forRequest(input), authAudience, authResource); + } +} diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AzureStorageProperties.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AzureStorageProperties.java new file mode 100644 index 0000000..4404e45 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/config/AzureStorageProperties.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.azure.storage.config; + +public final class AzureStorageProperties { + public static final String AUTH_TYPE = "jclouds.azureblob.auth"; + public static final String ACCOUNT = "jclouds.azureblob.account"; + public static final String TENANT_ID = "jclouds.azureblob.tenantId"; + + private AzureStorageProperties() { + throw new AssertionError("intentionally unimplemented"); + } +} diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java index c2cdf98..2d8f3b6 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java @@ -35,6 +35,7 @@ import javax.inject.Provider; import javax.inject.Singleton; import org.jclouds.Constants; +import org.jclouds.azure.storage.config.AuthType; import org.jclouds.azure.storage.util.storageurl.StorageUrlSupplier; import org.jclouds.crypto.Crypto; import org.jclouds.date.TimeStamp; @@ -47,6 +48,7 @@ import org.jclouds.http.Uris; import org.jclouds.http.Uris.UriBuilder; import org.jclouds.http.internal.SignatureWire; import org.jclouds.logging.Logger; +import org.jclouds.oauth.v2.filters.OAuthFilter; import org.jclouds.util.Strings2; import com.google.common.annotations.VisibleForTesting; @@ -80,6 +82,8 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter { private final HttpUtils utils; private final URI storageUrl; private final boolean isSAS; + private final AuthType authType; + private final OAuthFilter oAuthFilter; @Resource @Named(Constants.LOGGER_SIGNATURE) @@ -88,8 +92,9 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter { @Inject public SharedKeyLiteAuthentication(SignatureWire signatureWire, @org.jclouds.location.Provider Supplier<Credentials> creds, @TimeStamp Provider<String> timeStampProvider, - Crypto crypto, HttpUtils utils, @Named("sasAuth") boolean sasAuthentication, - StorageUrlSupplier storageUrlSupplier) { + Crypto crypto, HttpUtils utils, @Named("sasAuth") boolean sasAuthentication, + StorageUrlSupplier storageUrlSupplier, AuthType authType, + OAuthFilter oAuthFilter) { this.crypto = crypto; this.utils = utils; this.signatureWire = signatureWire; @@ -98,6 +103,8 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter { this.credential = creds.get().credential; this.timeStampProvider = timeStampProvider; this.isSAS = sasAuthentication; + this.authType = authType; + this.oAuthFilter = oAuthFilter; } /** @@ -105,11 +112,15 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter { * is used and applies the right filtering. */ public HttpRequest filter(HttpRequest request) throws HttpException { - request = this.isSAS ? filterSAS(request, this.credential) : filterKey(request); + if (this.authType == AuthType.AZURE_AD) { + request = this.oAuthFilter.filter(request); + } else { + request = this.isSAS ? filterSAS(request, this.credential) : filterKey(request); + } utils.logRequest(signatureLog, request, "<<"); return request; } - + /** * this filter method is applied only for the cases with SAS Authentication. */ diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/util/storageurl/StorageAccountInVhost.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/util/storageurl/StorageAccountInVhost.java index 6835650..0fafac6 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azure/storage/util/storageurl/StorageAccountInVhost.java +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/util/storageurl/StorageAccountInVhost.java @@ -17,12 +17,15 @@ package org.jclouds.azure.storage.util.storageurl; import com.google.common.base.Supplier; +import org.jclouds.azure.storage.config.AuthType; import org.jclouds.domain.Credentials; import org.jclouds.location.Provider; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Singleton; import java.net.URI; +import static org.jclouds.azure.storage.config.AzureStorageProperties.ACCOUNT; import static org.jclouds.azure.storage.util.storageurl.TrailingSlashUtil.ensureTrailingSlash; @Singleton @@ -30,11 +33,18 @@ public class StorageAccountInVhost implements StorageUrlSupplier { private final Supplier<URI> endpointSupplier; private final Supplier<Credentials> credentialsSupplier; + private final AuthType authType; + private final String account; @Inject - public StorageAccountInVhost(@Provider Supplier<URI> endpointSupplier, @Provider Supplier<Credentials> credentialsSupplier) { + public StorageAccountInVhost(@Provider Supplier<URI> endpointSupplier, + @Provider Supplier<Credentials> credentialsSupplier, + AuthType authType, + @Named(ACCOUNT) String account) { this.endpointSupplier = endpointSupplier; this.credentialsSupplier = credentialsSupplier; + this.authType = authType; + this.account = account; } @Override @@ -49,7 +59,10 @@ public class StorageAccountInVhost implements StorageUrlSupplier { } private String buildUri() { - return "https://" + credentialsSupplier.get().identity + ".blob.core.windows.net/"; + String account = this.account; + if (account == null) { + account = credentialsSupplier.get().identity; + } + return "https://" + account + ".blob.core.windows.net/"; } - } diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobApiMetadata.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobApiMetadata.java index 4b5f3eb..16b1d98 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobApiMetadata.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobApiMetadata.java @@ -16,15 +16,20 @@ */ package org.jclouds.azureblob; +import static org.jclouds.azure.storage.config.AzureStorageProperties.ACCOUNT; +import static org.jclouds.azure.storage.config.AzureStorageProperties.AUTH_TYPE; +import static org.jclouds.azure.storage.config.AzureStorageProperties.TENANT_ID; import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX; import static org.jclouds.reflect.Reflection2.typeToken; import java.net.URI; import java.util.Properties; +import org.jclouds.azure.storage.config.AuthType; import org.jclouds.azureblob.blobstore.config.AzureBlobStoreContextModule; import org.jclouds.azureblob.config.AzureBlobHttpApiModule; import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.oauth.v2.config.OAuthModule; import org.jclouds.rest.internal.BaseHttpApiMetadata; import com.google.common.collect.ImmutableSet; @@ -52,6 +57,9 @@ public class AzureBlobApiMetadata extends BaseHttpApiMetadata { public static Properties defaultProperties() { Properties properties = BaseHttpApiMetadata.defaultProperties(); properties.setProperty(PROPERTY_USER_METADATA_PREFIX, "x-ms-meta-"); + properties.setProperty(AUTH_TYPE, AuthType.AZURE_KEY.toString()); + properties.setProperty(ACCOUNT, ""); + properties.setProperty(TENANT_ID, ""); return properties; } @@ -67,7 +75,10 @@ public class AzureBlobApiMetadata extends BaseHttpApiMetadata { .documentation(URI.create("http://msdn.microsoft.com/en-us/library/dd135733.aspx")) .defaultProperties(AzureBlobApiMetadata.defaultProperties()) .view(typeToken(BlobStoreContext.class)) - .defaultModules(ImmutableSet.<Class<? extends Module>>of(AzureBlobHttpApiModule.class, AzureBlobStoreContextModule.class)); + .defaultModules(ImmutableSet.<Class<? extends Module>>of( + AzureBlobHttpApiModule.class, + AzureBlobStoreContextModule.class, + OAuthModule.class)); } @Override diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobProviderMetadata.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobProviderMetadata.java index 348fc94..c84a2fe 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobProviderMetadata.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobProviderMetadata.java @@ -24,6 +24,12 @@ import org.jclouds.providers.internal.BaseProviderMetadata; import com.google.auto.service.AutoService; +import static org.jclouds.azure.storage.config.AzureStorageProperties.ACCOUNT; +import static org.jclouds.azure.storage.config.AzureStorageProperties.TENANT_ID; +import static org.jclouds.oauth.v2.config.CredentialType.CLIENT_CREDENTIALS_SECRET; +import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE; +import static org.jclouds.oauth.v2.config.OAuthProperties.RESOURCE; + /** * Implementation of {@link org.jclouds.types.ProviderMetadata} for Microsoft Azure Blob Service. */ @@ -38,7 +44,7 @@ public class AzureBlobProviderMetadata extends BaseProviderMetadata { public Builder toBuilder() { return builder().fromProviderMetadata(this); } - + public AzureBlobProviderMetadata() { super(builder()); } @@ -49,15 +55,18 @@ public class AzureBlobProviderMetadata extends BaseProviderMetadata { public static Properties defaultProperties() { Properties properties = new Properties(); + properties.put("oauth.endpoint", "https://login.microsoft.com/${" + TENANT_ID + "}/oauth2/token"); + properties.put(RESOURCE, "https://storage.azure.com"); + properties.put(CREDENTIAL_TYPE, CLIENT_CREDENTIALS_SECRET.toString()); + properties.put(ACCOUNT, "${jclouds.identity}"); return properties; } public static class Builder extends BaseProviderMetadata.Builder { - protected Builder() { id("azureblob") .name("Microsoft Azure Blob Service") .apiMetadata(new AzureBlobApiMetadata()) - .endpoint("https://${jclouds.identity}.blob.core.windows.net") + .endpoint("https://${" + ACCOUNT + "}.blob.core.windows.net") .homepage(URI.create("http://www.microsoft.com/windowsazure/storage/")) .console(URI.create("https://windows.azure.com/default.aspx")) .linkedServices("azureblob", "azurequeue", "azuretable") diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureBlobMetadataToRequest.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureBlobMetadataToRequest.java index 0a384d2..cc1a4ab 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureBlobMetadataToRequest.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureBlobMetadataToRequest.java @@ -77,7 +77,7 @@ public class BindAzureBlobMetadataToRequest implements Binder { break; case BLOCK_BLOB: // see https://docs.microsoft.com/en-us/rest/api/storageservices/understanding-block-blobs--append-blobs--and-page-blobs - // see AzureBlobApiMetadata#version (current API version used is 2017-04-17) + // see AzureBlobApiMetadata#version (current API version used is 2017-11-09) checkArgument( checkNotNull(blob.getPayload().getContentMetadata().getContentLength(), "blob.getContentLength()") <= 256L * 1024 * 1024, "maximum size for put Blob is 256MB"); diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java index 9923898..f058576 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobRequestSigner.java @@ -26,6 +26,7 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +import org.jclouds.azure.storage.config.AuthType; import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication; import org.jclouds.azure.storage.util.storageurl.StorageUrlSupplier; import org.jclouds.blobstore.BlobRequestSigner; @@ -57,13 +58,14 @@ public class AzureBlobRequestSigner implements BlobRequestSigner { private final SharedKeyLiteAuthentication auth; private final String credential; private final boolean isSAS; + private final AuthType authType; @Inject public AzureBlobRequestSigner( BlobToHttpGetOptions blob2HttpGetOptions, @TimeStamp Provider<String> timeStampProvider, DateService dateService, SharedKeyLiteAuthentication auth, @org.jclouds.location.Provider Supplier<Credentials> creds, @Named("sasAuth") boolean sasAuthentication, - StorageUrlSupplier storageUriSupplier) + StorageUrlSupplier storageUriSupplier, AuthType authType) throws SecurityException, NoSuchMethodException { this.identity = creds.get().identity; this.credential = creds.get().credential; @@ -73,6 +75,7 @@ public class AzureBlobRequestSigner implements BlobRequestSigner { this.dateService = checkNotNull(dateService, "dateService"); this.auth = auth; this.isSAS = sasAuthentication; + this.authType = authType; } @Override @@ -192,12 +195,30 @@ public class AzureBlobRequestSigner implements BlobRequestSigner { .replaceHeader(HttpHeaders.DATE, nowString); request = setHeaders(request, method, options, contentLength, contentType); return request.build(); - } + } + + private HttpRequest signAD(String method, String container, String name, + @Nullable GetOptions options, long expires, + @Nullable Long contentLength, @Nullable String contentType) { + checkNotNull(method, "method"); + checkNotNull(container, "container"); + checkNotNull(name, "name"); + String nowString = timeStampProvider.get(); + HttpRequest.Builder request = HttpRequest.builder() + .method(method) + .endpoint(Uris.uriBuilder(storageUrl).appendPath(container).appendPath(name).build()) + .replaceHeader(HttpHeaders.DATE, nowString); + request = setHeaders(request, method, options, contentLength, contentType); + return request.build(); + } /** * modified sign() method, which acts depending on the Auth input. */ public HttpRequest sign(String method, String container, String name, @Nullable GetOptions options, long expires, @Nullable Long contentLength, @Nullable String contentType) { + if (authType == AuthType.AZURE_AD) { + return signAD(method, container, name, options, expires, contentLength, contentType); + } if (isSAS) { return signSAS(method, container, name, options, expires, contentLength, contentType); } diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/config/AzureBlobStoreContextModule.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/config/AzureBlobStoreContextModule.java index 25e9972..3e48ab5 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/config/AzureBlobStoreContextModule.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/config/AzureBlobStoreContextModule.java @@ -22,6 +22,7 @@ import com.google.common.cache.LoadingCache; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.Scopes; +import org.jclouds.azure.storage.config.AuthType; import org.jclouds.azureblob.AzureBlobClient; import org.jclouds.azureblob.blobstore.AzureBlobRequestSigner; import org.jclouds.azureblob.blobstore.AzureBlobStore; @@ -46,12 +47,12 @@ public class AzureBlobStoreContextModule extends AbstractModule { @Provides @Singleton - protected final LoadingCache<String, PublicAccess> containerAcls(final AzureBlobClient client, @Named("sasAuth") final boolean sasAuthentication) { + protected final LoadingCache<String, PublicAccess> containerAcls(final AzureBlobClient client, @Named("sasAuth") final boolean sasAuthentication, AuthType authType) { return CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build( new CacheLoader<String, PublicAccess>() { @Override public PublicAccess load(String container) throws CacheLoader.InvalidCacheLoadException { - if (!sasAuthentication) { + if (!sasAuthentication && authType == AuthType.AZURE_KEY) { return client.getPublicAccessForContainer(container); } throw new InsufficientAccessRightsException("SAS Authentication does not support getAcl and setAcl calls."); diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobHttpApiModule.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobHttpApiModule.java index 264ad44..82c7b29 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobHttpApiModule.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobHttpApiModule.java @@ -30,6 +30,8 @@ import java.util.List; import javax.inject.Named; +import com.google.inject.Scopes; +import org.jclouds.azure.storage.config.AzureStorageOAuthConfigFactory; import org.jclouds.azure.storage.handlers.AzureStorageClientErrorRetryHandler; import org.jclouds.azureblob.AzureBlobClient; import org.jclouds.azureblob.handlers.ParseAzureBlobErrorFromXmlContent; @@ -42,6 +44,8 @@ import org.jclouds.http.annotation.Redirection; import org.jclouds.http.annotation.ServerError; import org.jclouds.json.config.GsonModule.DateAdapter; import org.jclouds.json.config.GsonModule.Iso8601DateAdapter; +import org.jclouds.oauth.v2.config.OAuthConfigFactory; +import org.jclouds.oauth.v2.config.OAuthScopes; import org.jclouds.rest.ConfiguresHttpApi; import org.jclouds.rest.config.HttpApiModule; import org.jclouds.domain.Credentials; @@ -55,11 +59,12 @@ import com.google.inject.Provides; */ @ConfiguresHttpApi public class AzureBlobHttpApiModule extends HttpApiModule<AzureBlobClient> { - @Override protected void configure() { install(new AzureBlobModule()); bind(DateAdapter.class).to(Iso8601DateAdapter.class); + bind(OAuthScopes.class).toInstance(OAuthScopes.NoScopes.create()); + bind(OAuthConfigFactory.class).to(AzureStorageOAuthConfigFactory.class).in(Scopes.SINGLETON); super.configure(); } @@ -77,7 +82,7 @@ public class AzureBlobHttpApiModule extends HttpApiModule<AzureBlobClient> { * checks which Authentication type is used */ @Named("sasAuth") - @Provides + @Provides protected boolean authSAS(@org.jclouds.location.Provider Supplier<Credentials> creds) { String credential = creds.get().credential; String formattedCredential = credential.startsWith("?") ? credential.substring(1) : credential; diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobModule.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobModule.java index ffd4832..f0460ba 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobModule.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/config/AzureBlobModule.java @@ -17,8 +17,10 @@ package org.jclouds.azureblob.config; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Provider; +import org.jclouds.azure.storage.config.AuthType; import org.jclouds.azureblob.domain.AzureBlob; import org.jclouds.azureblob.domain.MutableBlobProperties; import org.jclouds.azureblob.domain.internal.AzureBlobImpl; @@ -28,6 +30,8 @@ import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.Scopes; +import static org.jclouds.azure.storage.config.AzureStorageProperties.AUTH_TYPE; + /** * Configures the domain object mappings needed for all Azure Blob implementations */ @@ -58,4 +62,9 @@ public class AzureBlobModule extends AbstractModule { return factory.create(null); } + @Inject + @Provides + final AuthType AuthTypeFromPropertyOrDefault(@Named(AUTH_TYPE) String authType) { + return AuthType.fromValue(authType); + } } diff --git a/providers/azureblob/src/test/java/org/jclouds/azure/storage/util/storageurl/StorageAccountInVhostTest.java b/providers/azureblob/src/test/java/org/jclouds/azure/storage/util/storageurl/StorageAccountInVhostTest.java index 5bf23e8..7e1f4a1 100644 --- a/providers/azureblob/src/test/java/org/jclouds/azure/storage/util/storageurl/StorageAccountInVhostTest.java +++ b/providers/azureblob/src/test/java/org/jclouds/azure/storage/util/storageurl/StorageAccountInVhostTest.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableSet; import com.google.inject.Module; import com.google.inject.util.Modules; import org.jclouds.ContextBuilder; +import org.jclouds.azure.storage.config.AuthType; import org.jclouds.azureblob.config.AppendAccountToEndpointModule; import org.jclouds.domain.Credentials; import org.jclouds.logging.config.NullLoggingModule; @@ -40,7 +41,9 @@ public class StorageAccountInVhostTest { StorageAccountInVhost target = new StorageAccountInVhost( () -> null, - () -> new Credentials(ACCOUNT, "creds") + () -> new Credentials(ACCOUNT, "creds"), + AuthType.AZURE_KEY, + null ); assertEquals(target.get().toString(), "https://foo.blob.core.windows.net/"); @@ -51,7 +54,9 @@ public class StorageAccountInVhostTest { StorageAccountInVhost target = new StorageAccountInVhost( () -> URI.create("https://foo2.blob.core.windows.net/"), - () -> new Credentials(ACCOUNT, "creds") + () -> new Credentials(ACCOUNT, "creds"), + AuthType.AZURE_KEY, + "" ); assertEquals(target.get().toString(), "https://foo2.blob.core.windows.net/"); @@ -62,7 +67,9 @@ public class StorageAccountInVhostTest { StorageAccountInVhost target = new StorageAccountInVhost( () -> URI.create("https://foo2.blob.core.windows.net/"), - () -> new Credentials(ACCOUNT, "creds") + () -> new Credentials(ACCOUNT, "creds"), + AuthType.AZURE_KEY, + "" ); assertEquals(target.get().toString(), "https://foo2.blob.core.windows.net/"); diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientAdTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientAdTest.java new file mode 100644 index 0000000..8462350 --- /dev/null +++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientAdTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.azureblob; + +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.Invokable; +import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication; +import org.jclouds.azure.storage.options.ListOptions; +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.internal.BaseRestAnnotationProcessingTest; +import org.jclouds.rest.internal.GeneratedHttpRequest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.Properties; + +import static org.jclouds.azure.storage.config.AzureStorageProperties.ACCOUNT; +import static org.jclouds.azure.storage.config.AzureStorageProperties.AUTH_TYPE; +import static org.jclouds.azure.storage.config.AzureStorageProperties.TENANT_ID; +import static org.jclouds.reflect.Reflection2.method; +import static org.testng.Assert.assertEquals; + +@Test(groups = "unit", testName = "AzureBlobClientAdTest") +public class AzureBlobClientAdTest extends BaseRestAnnotationProcessingTest<AzureBlobClient> { + @Override + protected void checkFilters(HttpRequest request) { + assertEquals(request.getFilters().size(), 1); + assertEquals(request.getFilters().get(0).getClass(), SharedKeyLiteAuthentication.class); + } + + @Override + public AzureBlobProviderMetadata createProviderMetadata() { + return new AzureBlobProviderMetadata(); + } + + @Override + protected Properties setupProperties() { + Properties adProperties = new Properties(); + adProperties.setProperty(TENANT_ID, "tenant"); + adProperties.setProperty(ACCOUNT, "ad-account"); + adProperties.setProperty(AUTH_TYPE, "azureAd"); + return adProperties; + } + + public void testListContainersAD() throws SecurityException, NoSuchMethodException, IOException { + Invokable<?, ?> method = method(AzureBlobClient.class, "listContainers", ListOptions[].class); + GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.of()); + + assertRequestLineEquals(request, "GET https://ad-account.blob.core.windows.net/?comp=list HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "x-ms-version: 2017-11-09\n"); + assertPayloadEquals(request, null, null, false); + } +} diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java index 7d727df..6734adf 100644 --- a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java +++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java @@ -19,6 +19,7 @@ package org.jclouds.azureblob; import static com.google.common.io.BaseEncoding.base16; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; +import static org.jclouds.azure.storage.config.AzureStorageProperties.AUTH_TYPE; import static org.jclouds.azure.storage.options.ListOptions.Builder.includeMetadata; import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withMetadata; import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withPublicAccess; @@ -36,6 +37,7 @@ import java.util.Map; import java.util.Set; import org.jclouds.azure.storage.AzureStorageResponseException; +import org.jclouds.azure.storage.config.AuthType; import org.jclouds.azure.storage.domain.BoundedSet; import org.jclouds.azure.storage.options.ListOptions; import org.jclouds.azureblob.domain.AccessTier; @@ -59,6 +61,7 @@ import org.jclouds.io.Payloads; import org.jclouds.util.Strings2; import org.jclouds.util.Throwables2; import org.jclouds.utils.TestUtils; +import org.testng.SkipException; import org.testng.annotations.Test; import com.google.common.base.Charsets; @@ -71,9 +74,7 @@ import com.google.common.io.ByteSource; @Test(groups = "live", singleThreaded = true) public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest { - public AzureBlobClientLiveTest() { - provider = "azureblob"; - } + public AzureBlobClientLiveTest() { provider = "azureblob"; } public AzureBlobClient getApi() { return view.unwrapApi(AzureBlobClient.class); @@ -111,8 +112,8 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest { long containerCount = response.size(); assertTrue(containerCount >= 1); ListBlobsResponse list = getApi().listBlobs(privateContainer); - assertEquals(list.getUrl(), URI.create(String.format("https://%s.blob.core.windows.net/%s", - view.unwrap().getIdentity(), privateContainer))); + assertThat(list.getUrl().toString()).endsWith( + String.format(".blob.core.windows.net/%s", privateContainer)); // TODO .. check to see the container actually exists } @@ -174,8 +175,7 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest { } } ListBlobsResponse list = getApi().listBlobs(); - assertEquals(list.getUrl(), URI.create(String.format("https://%s.blob.core.windows.net/$root", - view.unwrap().getIdentity()))); + assertThat(list.getUrl().toString()).endsWith(".blob.core.windows.net/$root"); } @Test @@ -388,6 +388,10 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest { @Test public void testGetSetACL() throws Exception { + String authType = System.getProperty(AUTH_TYPE); + if (authType != null && AuthType.fromValue(authType) == AuthType.AZURE_AD) { + throw new SkipException("Get/Set ACL unsupported with Azure AD"); + } AzureBlobClient client = getApi(); String blockContainer = CONTAINER_PREFIX + containerIndex.incrementAndGet(); client.createContainer(blockContainer);