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.
This commit is contained in:
Timur Alperovich 2021-08-09 23:46:24 -07:00 committed by Andrew Gaul
parent 519bee9f60
commit 0b68e8adee
16 changed files with 315 additions and 28 deletions

View File

@ -70,6 +70,12 @@
<artifactId>auto-service</artifactId> <artifactId>auto-service</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.apache.jclouds.api</groupId>
<artifactId>oauth</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>

View File

@ -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")));
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -35,6 +35,7 @@ import javax.inject.Provider;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.azure.storage.config.AuthType;
import org.jclouds.azure.storage.util.storageurl.StorageUrlSupplier; import org.jclouds.azure.storage.util.storageurl.StorageUrlSupplier;
import org.jclouds.crypto.Crypto; import org.jclouds.crypto.Crypto;
import org.jclouds.date.TimeStamp; import org.jclouds.date.TimeStamp;
@ -47,6 +48,7 @@ import org.jclouds.http.Uris;
import org.jclouds.http.Uris.UriBuilder; import org.jclouds.http.Uris.UriBuilder;
import org.jclouds.http.internal.SignatureWire; import org.jclouds.http.internal.SignatureWire;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.oauth.v2.filters.OAuthFilter;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -80,6 +82,8 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
private final HttpUtils utils; private final HttpUtils utils;
private final URI storageUrl; private final URI storageUrl;
private final boolean isSAS; private final boolean isSAS;
private final AuthType authType;
private final OAuthFilter oAuthFilter;
@Resource @Resource
@Named(Constants.LOGGER_SIGNATURE) @Named(Constants.LOGGER_SIGNATURE)
@ -88,8 +92,9 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
@Inject @Inject
public SharedKeyLiteAuthentication(SignatureWire signatureWire, public SharedKeyLiteAuthentication(SignatureWire signatureWire,
@org.jclouds.location.Provider Supplier<Credentials> creds, @TimeStamp Provider<String> timeStampProvider, @org.jclouds.location.Provider Supplier<Credentials> creds, @TimeStamp Provider<String> timeStampProvider,
Crypto crypto, HttpUtils utils, @Named("sasAuth") boolean sasAuthentication, Crypto crypto, HttpUtils utils, @Named("sasAuth") boolean sasAuthentication,
StorageUrlSupplier storageUrlSupplier) { StorageUrlSupplier storageUrlSupplier, AuthType authType,
OAuthFilter oAuthFilter) {
this.crypto = crypto; this.crypto = crypto;
this.utils = utils; this.utils = utils;
this.signatureWire = signatureWire; this.signatureWire = signatureWire;
@ -98,6 +103,8 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
this.credential = creds.get().credential; this.credential = creds.get().credential;
this.timeStampProvider = timeStampProvider; this.timeStampProvider = timeStampProvider;
this.isSAS = sasAuthentication; 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. * is used and applies the right filtering.
*/ */
public HttpRequest filter(HttpRequest request) throws HttpException { 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, "<<"); utils.logRequest(signatureLog, request, "<<");
return request; return request;
} }
/** /**
* this filter method is applied only for the cases with SAS Authentication. * this filter method is applied only for the cases with SAS Authentication.
*/ */

View File

@ -17,12 +17,15 @@
package org.jclouds.azure.storage.util.storageurl; package org.jclouds.azure.storage.util.storageurl;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import org.jclouds.azure.storage.config.AuthType;
import org.jclouds.domain.Credentials; import org.jclouds.domain.Credentials;
import org.jclouds.location.Provider; import org.jclouds.location.Provider;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.net.URI; import java.net.URI;
import static org.jclouds.azure.storage.config.AzureStorageProperties.ACCOUNT;
import static org.jclouds.azure.storage.util.storageurl.TrailingSlashUtil.ensureTrailingSlash; import static org.jclouds.azure.storage.util.storageurl.TrailingSlashUtil.ensureTrailingSlash;
@Singleton @Singleton
@ -30,11 +33,18 @@ public class StorageAccountInVhost implements StorageUrlSupplier {
private final Supplier<URI> endpointSupplier; private final Supplier<URI> endpointSupplier;
private final Supplier<Credentials> credentialsSupplier; private final Supplier<Credentials> credentialsSupplier;
private final AuthType authType;
private final String account;
@Inject @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.endpointSupplier = endpointSupplier;
this.credentialsSupplier = credentialsSupplier; this.credentialsSupplier = credentialsSupplier;
this.authType = authType;
this.account = account;
} }
@Override @Override
@ -49,7 +59,10 @@ public class StorageAccountInVhost implements StorageUrlSupplier {
} }
private String buildUri() { 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/";
} }
} }

View File

@ -16,15 +16,20 @@
*/ */
package org.jclouds.azureblob; 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.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
import static org.jclouds.reflect.Reflection2.typeToken; import static org.jclouds.reflect.Reflection2.typeToken;
import java.net.URI; import java.net.URI;
import java.util.Properties; import java.util.Properties;
import org.jclouds.azure.storage.config.AuthType;
import org.jclouds.azureblob.blobstore.config.AzureBlobStoreContextModule; import org.jclouds.azureblob.blobstore.config.AzureBlobStoreContextModule;
import org.jclouds.azureblob.config.AzureBlobHttpApiModule; import org.jclouds.azureblob.config.AzureBlobHttpApiModule;
import org.jclouds.blobstore.BlobStoreContext; import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.oauth.v2.config.OAuthModule;
import org.jclouds.rest.internal.BaseHttpApiMetadata; import org.jclouds.rest.internal.BaseHttpApiMetadata;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -52,6 +57,9 @@ public class AzureBlobApiMetadata extends BaseHttpApiMetadata {
public static Properties defaultProperties() { public static Properties defaultProperties() {
Properties properties = BaseHttpApiMetadata.defaultProperties(); Properties properties = BaseHttpApiMetadata.defaultProperties();
properties.setProperty(PROPERTY_USER_METADATA_PREFIX, "x-ms-meta-"); 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; return properties;
} }
@ -67,7 +75,10 @@ public class AzureBlobApiMetadata extends BaseHttpApiMetadata {
.documentation(URI.create("http://msdn.microsoft.com/en-us/library/dd135733.aspx")) .documentation(URI.create("http://msdn.microsoft.com/en-us/library/dd135733.aspx"))
.defaultProperties(AzureBlobApiMetadata.defaultProperties()) .defaultProperties(AzureBlobApiMetadata.defaultProperties())
.view(typeToken(BlobStoreContext.class)) .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 @Override

View File

@ -24,6 +24,12 @@ import org.jclouds.providers.internal.BaseProviderMetadata;
import com.google.auto.service.AutoService; 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. * Implementation of {@link org.jclouds.types.ProviderMetadata} for Microsoft Azure Blob Service.
*/ */
@ -38,7 +44,7 @@ public class AzureBlobProviderMetadata extends BaseProviderMetadata {
public Builder toBuilder() { public Builder toBuilder() {
return builder().fromProviderMetadata(this); return builder().fromProviderMetadata(this);
} }
public AzureBlobProviderMetadata() { public AzureBlobProviderMetadata() {
super(builder()); super(builder());
} }
@ -49,15 +55,18 @@ public class AzureBlobProviderMetadata extends BaseProviderMetadata {
public static Properties defaultProperties() { public static Properties defaultProperties() {
Properties properties = new Properties(); 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; return properties;
} }
public static class Builder extends BaseProviderMetadata.Builder { public static class Builder extends BaseProviderMetadata.Builder {
protected Builder() { protected Builder() {
id("azureblob") id("azureblob")
.name("Microsoft Azure Blob Service") .name("Microsoft Azure Blob Service")
.apiMetadata(new AzureBlobApiMetadata()) .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/")) .homepage(URI.create("http://www.microsoft.com/windowsazure/storage/"))
.console(URI.create("https://windows.azure.com/default.aspx")) .console(URI.create("https://windows.azure.com/default.aspx"))
.linkedServices("azureblob", "azurequeue", "azuretable") .linkedServices("azureblob", "azurequeue", "azuretable")

View File

@ -77,7 +77,7 @@ public class BindAzureBlobMetadataToRequest implements Binder {
break; break;
case BLOCK_BLOB: case BLOCK_BLOB:
// see https://docs.microsoft.com/en-us/rest/api/storageservices/understanding-block-blobs--append-blobs--and-page-blobs // 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( checkArgument(
checkNotNull(blob.getPayload().getContentMetadata().getContentLength(), "blob.getContentLength()") <= 256L * 1024 * 1024, checkNotNull(blob.getPayload().getContentMetadata().getContentLength(), "blob.getContentLength()") <= 256L * 1024 * 1024,
"maximum size for put Blob is 256MB"); "maximum size for put Blob is 256MB");

View File

@ -26,6 +26,7 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.jclouds.azure.storage.config.AuthType;
import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication; import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication;
import org.jclouds.azure.storage.util.storageurl.StorageUrlSupplier; import org.jclouds.azure.storage.util.storageurl.StorageUrlSupplier;
import org.jclouds.blobstore.BlobRequestSigner; import org.jclouds.blobstore.BlobRequestSigner;
@ -57,13 +58,14 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
private final SharedKeyLiteAuthentication auth; private final SharedKeyLiteAuthentication auth;
private final String credential; private final String credential;
private final boolean isSAS; private final boolean isSAS;
private final AuthType authType;
@Inject @Inject
public AzureBlobRequestSigner( public AzureBlobRequestSigner(
BlobToHttpGetOptions blob2HttpGetOptions, @TimeStamp Provider<String> timeStampProvider, BlobToHttpGetOptions blob2HttpGetOptions, @TimeStamp Provider<String> timeStampProvider,
DateService dateService, SharedKeyLiteAuthentication auth, DateService dateService, SharedKeyLiteAuthentication auth,
@org.jclouds.location.Provider Supplier<Credentials> creds, @Named("sasAuth") boolean sasAuthentication, @org.jclouds.location.Provider Supplier<Credentials> creds, @Named("sasAuth") boolean sasAuthentication,
StorageUrlSupplier storageUriSupplier) StorageUrlSupplier storageUriSupplier, AuthType authType)
throws SecurityException, NoSuchMethodException { throws SecurityException, NoSuchMethodException {
this.identity = creds.get().identity; this.identity = creds.get().identity;
this.credential = creds.get().credential; this.credential = creds.get().credential;
@ -73,6 +75,7 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
this.dateService = checkNotNull(dateService, "dateService"); this.dateService = checkNotNull(dateService, "dateService");
this.auth = auth; this.auth = auth;
this.isSAS = sasAuthentication; this.isSAS = sasAuthentication;
this.authType = authType;
} }
@Override @Override
@ -192,12 +195,30 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
.replaceHeader(HttpHeaders.DATE, nowString); .replaceHeader(HttpHeaders.DATE, nowString);
request = setHeaders(request, method, options, contentLength, contentType); request = setHeaders(request, method, options, contentLength, contentType);
return request.build(); 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. * 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) { 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) { if (isSAS) {
return signSAS(method, container, name, options, expires, contentLength, contentType); return signSAS(method, container, name, options, expires, contentLength, contentType);
} }

View File

@ -22,6 +22,7 @@ import com.google.common.cache.LoadingCache;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.Scopes; import com.google.inject.Scopes;
import org.jclouds.azure.storage.config.AuthType;
import org.jclouds.azureblob.AzureBlobClient; import org.jclouds.azureblob.AzureBlobClient;
import org.jclouds.azureblob.blobstore.AzureBlobRequestSigner; import org.jclouds.azureblob.blobstore.AzureBlobRequestSigner;
import org.jclouds.azureblob.blobstore.AzureBlobStore; import org.jclouds.azureblob.blobstore.AzureBlobStore;
@ -46,12 +47,12 @@ public class AzureBlobStoreContextModule extends AbstractModule {
@Provides @Provides
@Singleton @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( return CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build(
new CacheLoader<String, PublicAccess>() { new CacheLoader<String, PublicAccess>() {
@Override @Override
public PublicAccess load(String container) throws CacheLoader.InvalidCacheLoadException { public PublicAccess load(String container) throws CacheLoader.InvalidCacheLoadException {
if (!sasAuthentication) { if (!sasAuthentication && authType == AuthType.AZURE_KEY) {
return client.getPublicAccessForContainer(container); return client.getPublicAccessForContainer(container);
} }
throw new InsufficientAccessRightsException("SAS Authentication does not support getAcl and setAcl calls."); throw new InsufficientAccessRightsException("SAS Authentication does not support getAcl and setAcl calls.");

View File

@ -30,6 +30,8 @@ import java.util.List;
import javax.inject.Named; 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.azure.storage.handlers.AzureStorageClientErrorRetryHandler;
import org.jclouds.azureblob.AzureBlobClient; import org.jclouds.azureblob.AzureBlobClient;
import org.jclouds.azureblob.handlers.ParseAzureBlobErrorFromXmlContent; 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.http.annotation.ServerError;
import org.jclouds.json.config.GsonModule.DateAdapter; import org.jclouds.json.config.GsonModule.DateAdapter;
import org.jclouds.json.config.GsonModule.Iso8601DateAdapter; 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.ConfiguresHttpApi;
import org.jclouds.rest.config.HttpApiModule; import org.jclouds.rest.config.HttpApiModule;
import org.jclouds.domain.Credentials; import org.jclouds.domain.Credentials;
@ -55,11 +59,12 @@ import com.google.inject.Provides;
*/ */
@ConfiguresHttpApi @ConfiguresHttpApi
public class AzureBlobHttpApiModule extends HttpApiModule<AzureBlobClient> { public class AzureBlobHttpApiModule extends HttpApiModule<AzureBlobClient> {
@Override @Override
protected void configure() { protected void configure() {
install(new AzureBlobModule()); install(new AzureBlobModule());
bind(DateAdapter.class).to(Iso8601DateAdapter.class); bind(DateAdapter.class).to(Iso8601DateAdapter.class);
bind(OAuthScopes.class).toInstance(OAuthScopes.NoScopes.create());
bind(OAuthConfigFactory.class).to(AzureStorageOAuthConfigFactory.class).in(Scopes.SINGLETON);
super.configure(); super.configure();
} }
@ -77,7 +82,7 @@ public class AzureBlobHttpApiModule extends HttpApiModule<AzureBlobClient> {
* checks which Authentication type is used * checks which Authentication type is used
*/ */
@Named("sasAuth") @Named("sasAuth")
@Provides @Provides
protected boolean authSAS(@org.jclouds.location.Provider Supplier<Credentials> creds) { protected boolean authSAS(@org.jclouds.location.Provider Supplier<Credentials> creds) {
String credential = creds.get().credential; String credential = creds.get().credential;
String formattedCredential = credential.startsWith("?") ? credential.substring(1) : credential; String formattedCredential = credential.startsWith("?") ? credential.substring(1) : credential;

View File

@ -17,8 +17,10 @@
package org.jclouds.azureblob.config; package org.jclouds.azureblob.config;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider; import javax.inject.Provider;
import org.jclouds.azure.storage.config.AuthType;
import org.jclouds.azureblob.domain.AzureBlob; import org.jclouds.azureblob.domain.AzureBlob;
import org.jclouds.azureblob.domain.MutableBlobProperties; import org.jclouds.azureblob.domain.MutableBlobProperties;
import org.jclouds.azureblob.domain.internal.AzureBlobImpl; 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.Provides;
import com.google.inject.Scopes; 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 * Configures the domain object mappings needed for all Azure Blob implementations
*/ */
@ -58,4 +62,9 @@ public class AzureBlobModule extends AbstractModule {
return factory.create(null); return factory.create(null);
} }
@Inject
@Provides
final AuthType AuthTypeFromPropertyOrDefault(@Named(AUTH_TYPE) String authType) {
return AuthType.fromValue(authType);
}
} }

View File

@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.util.Modules; import com.google.inject.util.Modules;
import org.jclouds.ContextBuilder; import org.jclouds.ContextBuilder;
import org.jclouds.azure.storage.config.AuthType;
import org.jclouds.azureblob.config.AppendAccountToEndpointModule; import org.jclouds.azureblob.config.AppendAccountToEndpointModule;
import org.jclouds.domain.Credentials; import org.jclouds.domain.Credentials;
import org.jclouds.logging.config.NullLoggingModule; import org.jclouds.logging.config.NullLoggingModule;
@ -40,7 +41,9 @@ public class StorageAccountInVhostTest {
StorageAccountInVhost target = new StorageAccountInVhost( StorageAccountInVhost target = new StorageAccountInVhost(
() -> null, () -> null,
() -> new Credentials(ACCOUNT, "creds") () -> new Credentials(ACCOUNT, "creds"),
AuthType.AZURE_KEY,
null
); );
assertEquals(target.get().toString(), "https://foo.blob.core.windows.net/"); assertEquals(target.get().toString(), "https://foo.blob.core.windows.net/");
@ -51,7 +54,9 @@ public class StorageAccountInVhostTest {
StorageAccountInVhost target = new StorageAccountInVhost( StorageAccountInVhost target = new StorageAccountInVhost(
() -> URI.create("https://foo2.blob.core.windows.net/"), () -> 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/"); assertEquals(target.get().toString(), "https://foo2.blob.core.windows.net/");
@ -62,7 +67,9 @@ public class StorageAccountInVhostTest {
StorageAccountInVhost target = new StorageAccountInVhost( StorageAccountInVhost target = new StorageAccountInVhost(
() -> URI.create("https://foo2.blob.core.windows.net/"), () -> 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/"); assertEquals(target.get().toString(), "https://foo2.blob.core.windows.net/");

View File

@ -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);
}
}

View File

@ -19,6 +19,7 @@ package org.jclouds.azureblob;
import static com.google.common.io.BaseEncoding.base16; import static com.google.common.io.BaseEncoding.base16;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; 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.azure.storage.options.ListOptions.Builder.includeMetadata;
import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withMetadata; import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withMetadata;
import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withPublicAccess; import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withPublicAccess;
@ -36,6 +37,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.jclouds.azure.storage.AzureStorageResponseException; 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.domain.BoundedSet;
import org.jclouds.azure.storage.options.ListOptions; import org.jclouds.azure.storage.options.ListOptions;
import org.jclouds.azureblob.domain.AccessTier; import org.jclouds.azureblob.domain.AccessTier;
@ -59,6 +61,7 @@ import org.jclouds.io.Payloads;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import org.jclouds.util.Throwables2; import org.jclouds.util.Throwables2;
import org.jclouds.utils.TestUtils; import org.jclouds.utils.TestUtils;
import org.testng.SkipException;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
@ -71,9 +74,7 @@ import com.google.common.io.ByteSource;
@Test(groups = "live", singleThreaded = true) @Test(groups = "live", singleThreaded = true)
public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest { public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
public AzureBlobClientLiveTest() { public AzureBlobClientLiveTest() { provider = "azureblob"; }
provider = "azureblob";
}
public AzureBlobClient getApi() { public AzureBlobClient getApi() {
return view.unwrapApi(AzureBlobClient.class); return view.unwrapApi(AzureBlobClient.class);
@ -111,8 +112,8 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
long containerCount = response.size(); long containerCount = response.size();
assertTrue(containerCount >= 1); assertTrue(containerCount >= 1);
ListBlobsResponse list = getApi().listBlobs(privateContainer); ListBlobsResponse list = getApi().listBlobs(privateContainer);
assertEquals(list.getUrl(), URI.create(String.format("https://%s.blob.core.windows.net/%s", assertThat(list.getUrl().toString()).endsWith(
view.unwrap().getIdentity(), privateContainer))); String.format(".blob.core.windows.net/%s", privateContainer));
// TODO .. check to see the container actually exists // TODO .. check to see the container actually exists
} }
@ -174,8 +175,7 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
} }
} }
ListBlobsResponse list = getApi().listBlobs(); ListBlobsResponse list = getApi().listBlobs();
assertEquals(list.getUrl(), URI.create(String.format("https://%s.blob.core.windows.net/$root", assertThat(list.getUrl().toString()).endsWith(".blob.core.windows.net/$root");
view.unwrap().getIdentity())));
} }
@Test @Test
@ -388,6 +388,10 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
@Test @Test
public void testGetSetACL() throws Exception { 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(); AzureBlobClient client = getApi();
String blockContainer = CONTAINER_PREFIX + containerIndex.incrementAndGet(); String blockContainer = CONTAINER_PREFIX + containerIndex.incrementAndGet();
client.createContainer(blockContainer); client.createContainer(blockContainer);