From f27493d5ae3493184a5f9858a59fc93baf4f0da0 Mon Sep 17 00:00:00 2001 From: rickle-msft Date: Tue, 24 Apr 2018 13:36:25 -0700 Subject: [PATCH 1/3] Added worm support --- ChangeLog.txt | 3 ++ .../storage/blob/CloudBlobClientTests.java | 4 ++ .../storage/blob/CloudBlobContainerTests.java | 6 +++ .../storage/blob/CloudBlockBlobTests.java | 2 + .../microsoft/azure/storage/Constants.java | 2 +- .../azure/storage/blob/BlobConstants.java | 30 ++++++++++++ .../storage/blob/BlobContainerProperties.java | 48 +++++++++++++++++++ .../azure/storage/blob/BlobListHandler.java | 3 ++ .../azure/storage/blob/BlobProperties.java | 24 ++++++++++ .../azure/storage/blob/BlobResponse.java | 17 +++++++ .../storage/blob/ContainerListHandler.java | 6 +++ 11 files changed, 144 insertions(+), 1 deletion(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 1a71566fe..55040b5e0 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,6 @@ +XXXX.XX.XX Version X.X.X + * Support for write-once read-many containers. + 2018.02.05 Version 7.0.0 * Support for 2017-07-29 REST version. Please see our REST api documentation and blogs for information about the related added features. * Added support for soft delete feature. If a delete retention policy is enabled through the set service properties API, then blobs or snapshots can be deleted softly and retained for a specified number of days, before being permanently removed by garbage collection. diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientTests.java index fbf8c881e..a074ccf1d 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobClientTests.java @@ -77,6 +77,10 @@ public void testListContainers() throws StorageException, URISyntaxException { for (final CloudBlobContainer container : segment.getResults()) { container.downloadAttributes(); assertEquals(CloudBlobContainer.class, container.getClass()); + assertNotNull(container.getProperties().hasImmutabilityPolicy()); + assertNotNull(container.getProperties().hasLegalHold()); + assertFalse(container.getProperties().hasImmutabilityPolicy()); + assertFalse(container.getProperties().hasLegalHold()); containerList.remove(container.getName()); } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java index e6183e14c..c74a00ae9 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlobContainerTests.java @@ -316,6 +316,10 @@ public void testCloudBlobContainerExists() throws StorageException { this.container.create(); assertTrue(this.container.exists()); assertNotNull(this.container.getProperties().getEtag()); + assertNotNull(this.container.getProperties().hasImmutabilityPolicy()); + assertNotNull(this.container.getProperties().hasLegalHold()); + assertFalse(this.container.getProperties().hasImmutabilityPolicy()); + assertFalse(this.container.getProperties().hasLegalHold()); this.container.delete(); assertFalse(this.container.exists()); @@ -549,6 +553,8 @@ public void testCloudBlobContainerListBlobs() throws StorageException, IOExcepti EnumSet.noneOf(BlobListingDetails.class), 150, token, null, null); for (ListBlobItem blob : result.getResults()) { assertEquals(CloudBlockBlob.class, blob.getClass()); + assertNotNull(((CloudBlockBlob)blob).getProperties().getCreatedTime()); + assertTrue(((CloudBlockBlob)blob).getProperties().getCreatedTime().before(new Date())); assertTrue(blobNames.remove(((CloudBlockBlob) blob).getName())); } token = result.getContinuationToken(); diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java index 54c26e442..36b14549f 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java @@ -99,6 +99,8 @@ public void testBlockBlobCreate() throws StorageException, URISyntaxException, I // Create again (should succeed) blob.uploadText("text"); assertTrue(blob.exists()); + assertNotNull(blob.getProperties().getCreatedTime()); + assertTrue(blob.getProperties().getCreatedTime().before(new Date())); // Create again, specifying not to if it already exists // This should fail diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java index 218071f30..418adee3b 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -650,7 +650,7 @@ public static class HeaderConstants { /** * The current storage version header value. */ - public static final String TARGET_STORAGE_VERSION = "2017-07-29"; + public static final String TARGET_STORAGE_VERSION = "2017-11-09"; /** * The header that specifies the next visible time for a queue message. diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java index f9baadba8..0bb3d943a 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java @@ -150,6 +150,16 @@ final class BlobConstants { */ public static final String CONTENT_TYPE_HEADER = Constants.PREFIX_FOR_STORAGE_HEADER + "blob-content-type"; + /** + * The header that specifies the blob creation time. + */ + public static final String CREATION_TIME_HEADER = Constants.PREFIX_FOR_STORAGE_HEADER + "creation-time"; + + /** + * XML element for the creation time. + */ + public static final String CREATION_TIME_ELEMENT = "Creation-Time"; + /** * The number of default concurrent requests for parallel operation. */ @@ -170,6 +180,26 @@ final class BlobConstants { */ public static final int DEFAULT_POLLING_INTERVAL_IN_SECONDS = 30; + /** + * The header that specifies the immutability policy for the resource. + */ + public static final String HAS_IMMUTABILITY_POLICY_HEADER = Constants.PREFIX_FOR_STORAGE_HEADER + "has-immutability-policy"; + + /** + * The header that specifies the legal hold value for the resource. + */ + public static final String HAS_LEGAL_HOLD_HEADER = Constants.PREFIX_FOR_STORAGE_HEADER + "has-legal-hold"; + + /** + * XML element for immutability policy. + */ + public static final String HAS_IMMUTABILITY_POLICY_ELEMENT = "HasImmutabilityPolicy"; + + /** + * XML element for legal hold. + */ + public static final String HAS_LEGAL_HOLD_ELEMENT = "HasLegalHold"; + /** * XML element for the latest. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobContainerProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobContainerProperties.java index 1cc05c553..d77b6895f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobContainerProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobContainerProperties.java @@ -28,6 +28,16 @@ public final class BlobContainerProperties { */ private String etag; + /** + * Represents whether the container has an immutability policy. + */ + private Boolean hasImmutabilityPolicy; + + /** + * Represents whether the container has a legal hold. + */ + private Boolean hasLegalHold; + /** * Represents the container's last-modified time. */ @@ -69,6 +79,24 @@ public String getEtag() { return this.etag; } + /** + * Gets the hasImmutabilityPolicy value of the container. + * + * @return A Boolean which represents the hasImmutabilityPolicy value. + */ + public Boolean hasImmutabilityPolicy() { + return this.hasImmutabilityPolicy; + } + + /** + * Gets the hasLegalHold value of the container. + * + * @return A Boolean which represents the hasLegalHold value. + */ + public Boolean hasLegalHold() { + return this.hasLegalHold; + } + /** * Gets the last modified time on the container. * @@ -128,6 +156,26 @@ protected void setEtag(final String etag) { this.etag = etag; } + /** + * Sets the hasImmutabilityPolicy value on the container. + * + * @param hasImmutabilityPolicy + * A Boolean which represents the hasImmutabilityProperty value to set. + */ + protected void setHasImmutabilityPolicy(final Boolean hasImmutabilityPolicy) { + this.hasImmutabilityPolicy = hasImmutabilityPolicy; + } + + /** + * Sets the hasLegalHold value on the container. + * + * @param hasLegalHold + * A Boolean which represents the hasLegalHold value to set. + */ + protected void setHasLegalHold(final Boolean hasLegalHold) { + this.hasLegalHold= hasLegalHold; + } + /** * Sets the last modified time on the container. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java index b8da99d66..056add461 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java @@ -366,5 +366,8 @@ else if (BlobConstants.DELETED_TIME.equals(currentNode)) { else if (BlobConstants.REMAINING_RETENTION_DAYS.equals(currentNode)) { this.properties.setRemainingRetentionDays(Integer.parseInt(value)); } + else if (BlobConstants.CREATION_TIME_ELEMENT.equals(currentNode)) { + this.properties.setCreatedTime(Utility.parseRFC1123DateFromStringInGMT(value)); + } } } \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java index 15f62bba1..a22d293ca 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java @@ -72,6 +72,11 @@ public final class BlobProperties { */ private CopyState copyState; + /** + * Represents the creation time for the blob, expressed as a UTC value. + */ + private Date createdTime; + /** * Represents the blob's ETag value. */ @@ -176,6 +181,7 @@ public BlobProperties(final BlobProperties other) { this.contentMD5 = other.contentMD5; this.contentType = other.contentType; this.copyState = other.copyState; + this.createdTime = other.createdTime; this.etag = other.etag; this.isBlobTierInferredTier = other.isBlobTierInferredTier; this.isIncrementalCopy = other.isIncrementalCopy; @@ -288,6 +294,14 @@ public CopyState getCopyState() { return this.copyState; } + /** + * Gets the time when the blob was created. + * @return A {@link java.util.Date} object which represents the time when the blob was created. + */ + public Date getCreatedTime() { + return this.createdTime; + } + /** * Gets the ETag value for the blob. *

@@ -521,6 +535,16 @@ protected void setCopyState(final CopyState copyState) { this.copyState = copyState; } + /** + * Sets the createdTime value for the blob + * + * @param createdTime + * A Date which represents the time when the blob was created. + */ + protected void setCreatedTime(final Date createdTime) { + this.createdTime = createdTime; + } + /** * Sets the ETag value for the blob. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java index 33ff7c387..cd6888375 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java @@ -172,6 +172,12 @@ else if (properties.getPremiumPageBlobTier() != null || properties.getStandardB properties.setTierChangeTime(new Date(tierChangeTime)); } + // Get the creation time of the blob. + final long creationTime = request.getHeaderFieldDate(BlobConstants.CREATION_TIME_HEADER, 0); + if (creationTime != 0) { + properties.setCreatedTime(new Date(creationTime)); + } + final String incrementalCopyHeaderString = request.getHeaderField(Constants.HeaderConstants.INCREMENTAL_COPY); if (!Utility.isNullOrEmpty(incrementalCopyHeaderString)) { @@ -222,6 +228,17 @@ public static BlobContainerAttributes getBlobContainerAttributes(final HttpURLCo containerProperties.setPublicAccess(getPublicAccessLevel(request)); + // Worm policy + String hasImmutability = request.getHeaderField(BlobConstants.HAS_IMMUTABILITY_POLICY_HEADER); + if (!Utility.isNullOrEmpty(hasImmutability)) { + containerProperties.setHasImmutabilityPolicy(Boolean.parseBoolean(hasImmutability)); + } + + String hasLegalHold = request.getHeaderField(BlobConstants.HAS_LEGAL_HOLD_HEADER); + if (!Utility.isNullOrEmpty(hasLegalHold)) { + containerProperties.setHasLegalHold(Boolean.parseBoolean(hasLegalHold)); + } + return containerAttributes; } diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/ContainerListHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/ContainerListHandler.java index 3adeefe21..a7d99b141 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/ContainerListHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/ContainerListHandler.java @@ -203,5 +203,11 @@ else if (currentNode.equals(Constants.PUBLIC_ACCESS_ELEMENT)) { throw new SAXException(SR.INVALID_RESPONSE_RECEIVED); } } + else if (currentNode.equals(BlobConstants.HAS_IMMUTABILITY_POLICY_ELEMENT)) { + this.attributes.getProperties().setHasImmutabilityPolicy(Boolean.parseBoolean(value)); + } + else if (currentNode.equals(BlobConstants.HAS_LEGAL_HOLD_ELEMENT)) { + this.attributes.getProperties().setHasLegalHold(Boolean.parseBoolean(value)); + } } } From a36aaa56fd1b928e381b18c712767b9cbf64edb1 Mon Sep 17 00:00:00 2001 From: zezha-msft Date: Sun, 4 Feb 2018 00:06:28 -0800 Subject: [PATCH 2/3] Added support for token authentication for HTTPS requests --- ChangeLog.txt | 4 +- .../res/TestConfigurations.xml | 3 + .../microsoft/azure/storage/OAuthTests.java | 81 ++++++++++++++++ .../com/microsoft/azure/storage/Tenant.java | 29 +++++- .../microsoft/azure/storage/TestHelper.java | 61 +++++++++++- .../azure/storage/CloudStorageAccount.java | 5 + .../microsoft/azure/storage/Constants.java | 5 + .../azure/storage/StorageCredentials.java | 2 +- .../storage/StorageCredentialsToken.java | 96 +++++++++++++++++++ .../core/StorageCredentialsHelper.java | 23 +++-- pom.xml | 6 ++ 11 files changed, 303 insertions(+), 12 deletions(-) create mode 100644 microsoft-azure-storage-test/src/com/microsoft/azure/storage/OAuthTests.java create mode 100644 microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentialsToken.java diff --git a/ChangeLog.txt b/ChangeLog.txt index 55040b5e0..5d20297ed 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,4 +1,6 @@ -XXXX.XX.XX Version X.X.X +2018.05.07 Version 7.1.0 + * Support for 2017-11-09 REST version. Please see our REST API documentation and blogs for information about the related added features. + * Added OAuth token support for authentication with HTTPS requests. * Support for write-once read-many containers. 2018.02.05 Version 7.0.0 diff --git a/microsoft-azure-storage-test/res/TestConfigurations.xml b/microsoft-azure-storage-test/res/TestConfigurations.xml index edce1b7f2..9b52bf728 100644 --- a/microsoft-azure-storage-test/res/TestConfigurations.xml +++ b/microsoft-azure-storage-test/res/TestConfigurations.xml @@ -26,6 +26,9 @@ http://[ACCOUNT]-secondary.blob.core.windows.net http://[ACCOUNT]-secondary.queue.core.windows.net http://[ACCOUNT]-secondary.table.core.windows.net + [APPLICATION_ID] + [APPLICATION_SECRET] + [TENANT_ID] \ No newline at end of file diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/OAuthTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/OAuthTests.java new file mode 100644 index 000000000..be5562100 --- /dev/null +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/OAuthTests.java @@ -0,0 +1,81 @@ +package com.microsoft.azure.storage; + +import com.microsoft.azure.storage.blob.CloudBlobClient; +import com.microsoft.azure.storage.queue.CloudQueueClient; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import javax.naming.ServiceUnavailableException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * OAuth tests. + */ +public class OAuthTests { + /** + * Test OAuth with respect to blob client + */ + @Test + @Category({ TestRunners.DevFabricTests.class, TestRunners.DevStoreTests.class, TestRunners.CloudTests.class }) + public void testOAuthTokenWithBlobClient() throws StorageException, URISyntaxException, MalformedURLException, InterruptedException, ExecutionException, ServiceUnavailableException { + // get the standard test account + CloudStorageAccount account = TestHelper.getAccount(); + + // create a token credential and replace the account's credential + StorageCredentialsToken storageCredentialsToken = new StorageCredentialsToken(TestHelper.getAccountName(), TestHelper.generateOAuthToken()); + account.setCredentials(storageCredentialsToken); + + // test to make sure authentication is working + CloudBlobClient blobClient = account.createCloudBlobClient(); + blobClient.downloadServiceProperties(); + + // change the token to see it fail + try { + storageCredentialsToken.updateToken("BLA"); + blobClient.downloadServiceProperties(); + fail(); + } catch (StorageException e) { + assertEquals("AuthenticationFailed", e.getErrorCode()); + } + + // change the token again to see it succeed + storageCredentialsToken.updateToken(TestHelper.generateOAuthToken()); + blobClient.downloadServiceProperties(); + } + + /** + * Test OAuth with respect to queue client + */ + @Test + @Category({ TestRunners.DevFabricTests.class, TestRunners.DevStoreTests.class, TestRunners.CloudTests.class }) + public void testOAuthTokenWithQueueClient() throws StorageException, URISyntaxException, MalformedURLException, InterruptedException, ExecutionException, ServiceUnavailableException { + // get the standard test account + CloudStorageAccount account = TestHelper.getAccount(); + + // create a token credential and replace the account's credential + StorageCredentialsToken storageCredentialsToken = new StorageCredentialsToken(TestHelper.getAccountName(), TestHelper.generateOAuthToken()); + account.setCredentials(storageCredentialsToken); + + // test to make sure authentication is working + CloudQueueClient queueClient = account.createCloudQueueClient(); + queueClient.downloadServiceProperties(); + + // change the token to see it fail + try { + storageCredentialsToken.updateToken("BLA"); + queueClient.downloadServiceProperties(); + fail(); + } catch (StorageException e) { + assertEquals("AuthenticationFailed", e.getErrorCode()); + } + + // change the token again to see it succeed + storageCredentialsToken.updateToken(TestHelper.generateOAuthToken()); + queueClient.downloadServiceProperties(); + } +} diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/Tenant.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/Tenant.java index 2c111b132..753ab77b8 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/Tenant.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/Tenant.java @@ -32,6 +32,9 @@ public class Tenant { private Integer fileHttpsPortOverride = null; private Integer queueHttpsPortOverride = null; private Integer tableHttpsPortOverride = null; + private String activeDirectoryApplicationId; + private String activeDirectoryApplicationSecret; + private String activeDirectoryTenantId; public String getTenantName() { return this.tenantName; @@ -92,7 +95,31 @@ public Integer getQueueHttpsPortOverride() { public Integer getTableHttpsPortOverride() { return this.tableHttpsPortOverride; } - + + public String getActiveDirectoryApplicationId() { + return this.activeDirectoryApplicationId; + } + + public String getActiveDirectoryApplicationSecret() { + return this.activeDirectoryApplicationSecret; + } + + public String getActiveDirectoryTenantId() { + return this.activeDirectoryTenantId; + } + + public void setActiveDirectoryApplicationId(String activeDirectoryApplicationId) { + this.activeDirectoryApplicationId = activeDirectoryApplicationId; + } + + public void setActiveDirectoryApplicationSecret(String activeDirectoryApplicationSecret) { + this.activeDirectoryApplicationSecret = activeDirectoryApplicationSecret; + } + + public void setActiveDirectoryTenantId(String activeDirectoryTenantId) { + this.activeDirectoryTenantId = activeDirectoryTenantId; + } + void setTenantName(String tenantName) { this.tenantName = tenantName; } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java index edcf55972..18fc9246f 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -32,14 +33,22 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Random; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; +import javax.naming.ServiceUnavailableException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import com.microsoft.aad.adal4j.AuthenticationContext; +import com.microsoft.aad.adal4j.AuthenticationResult; +import com.microsoft.aad.adal4j.ClientCredential; import org.junit.AssumptionViolatedException; import org.w3c.dom.DOMException; import org.w3c.dom.Document; @@ -67,6 +76,47 @@ public class TestHelper { private final static boolean enableFiddler = false; private final static boolean requireSecondaryEndpoint = false; + public static String generateOAuthToken() throws MalformedURLException, InterruptedException, ExecutionException, ServiceUnavailableException, StorageException { + // if the test configuration has not been read in yet, trigger a getAccount to read in the information + if(tenant == null) { + getAccount(); + } + + // this boiler plate code is from the ADAL sample + String authority = String.format("https://login.microsoftonline.com/%s/oauth2/token", + tenant.getActiveDirectoryTenantId()); + ClientCredential credential = new ClientCredential(tenant.getActiveDirectoryApplicationId(), + tenant.getActiveDirectoryApplicationSecret()); + + ExecutorService service = null; + AuthenticationResult result; + + try { + service = Executors.newFixedThreadPool(1); + AuthenticationContext context = new AuthenticationContext(authority, true, service); + Future future = context.acquireToken("https://storage.azure.com", credential, null); + result = future.get(); + } finally { + if(service != null) { + service.shutdown(); + } + } + + if (result == null) { + throw new ServiceUnavailableException("authentication result was null"); + } + return result.getAccessToken(); + } + + public static String getAccountName() throws StorageException { + // if the test configuration has not been read in yet, trigger a getAccount to read in the information + if(tenant == null) { + getAccount(); + } + + return tenant.getAccountName(); + } + public static CloudBlobClient createCloudBlobClient() throws StorageException { CloudBlobClient client = getAccount().createCloudBlobClient(); return client; @@ -305,7 +355,7 @@ public static void verifyServiceStats(ServiceStats stats) { } } - private static CloudStorageAccount getAccount() throws StorageException { + public static CloudStorageAccount getAccount() throws StorageException { // Only do this the first time TestBase is called as storage account is static if (account == null) { //enable fiddler @@ -502,6 +552,15 @@ else if (name.equals("TableHttpsPortOverride")) { else if (name.equals("FileHttpsPortOverride")) { tenant.setFileHttpsPortOverride(Integer.parseInt(node.getTextContent())); } + else if (name.equals("ActiveDirectoryApplicationId")) { + tenant.setActiveDirectoryApplicationId(node.getTextContent()); + } + else if (name.equals("ActiveDirectoryApplicationSecret")) { + tenant.setActiveDirectoryApplicationSecret(node.getTextContent()); + } + else if (name.equals("ActiveDirectoryTenantId")) { + tenant.setActiveDirectoryTenantId(node.getTextContent()); + } else if (!premiumBlob){ throw new IllegalArgumentException(String.format( "Invalid child of TenantConfiguration with name: %s", name)); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java index a03fd55f1..2c0fce621 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java @@ -46,6 +46,11 @@ public final class CloudStorageAccount { */ protected static final String ACCOUNT_KEY_NAME = "AccountKey"; + /** + * Represents the setting name for the token credential. + */ + protected static final String ACCOUNT_TOKEN_NAME = "AccountToken"; + /** * Represents the setting name for the account name. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java index 418adee3b..52311baca 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -314,6 +314,11 @@ public static class HeaderConstants { */ public static final String AUTHORIZATION = "Authorization"; + /** + * The keyword used for bearer token authorization. + */ + public static final String BEARER = "Bearer"; + /** * The format string for specifying ranges with only begin offset. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentials.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentials.java index bb6b8d972..3b00b79f7 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentials.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentials.java @@ -25,7 +25,7 @@ /** * Represents a set of credentials used to authenticate access to a Microsoft Azure storage account. This is the base - * class for the {@link StorageCredentialsAccountAndKey} and {@link StorageCredentialsSharedAccessSignature} classes. + * class for the {@link StorageCredentialsAccountAndKey}, {@link StorageCredentialsToken}, and {@link StorageCredentialsSharedAccessSignature} classes. */ public abstract class StorageCredentials { diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentialsToken.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentialsToken.java new file mode 100644 index 000000000..fe337083f --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentialsToken.java @@ -0,0 +1,96 @@ +package com.microsoft.azure.storage; + +import java.net.URI; + +/** + * Represents storage account credentials, based on an authentication token, for accessing the Microsoft Azure + * storage services. + */ +public final class StorageCredentialsToken extends StorageCredentials{ + /** + * Stores the token for the credentials. + */ + private volatile String token; + + /** + * Stores the account name. + */ + private volatile String accountName; + + /** + * Creates an instance of the StorageCredentialsToken class, using the specified token. + * Token credentials must only be used with HTTPS requests on the blob and queue services. + * The specified token is stored as a String. + * + * @param token + * A String that represents the token. + */ + public StorageCredentialsToken(String accountName, String token) { + this.accountName = accountName; + this.token = token; + } + + /** + * Gets whether this StorageCredentials object only allows access via HTTPS. + * + * @return A boolean representing whether this StorageCredentials + * object only allows access via HTTPS. + */ + @Override + public boolean isHttpsOnly() { + return true; + } + + /** + * Gets the token. + * + * @return A String that contains the token. + */ + public String getToken() { + return this.token; + } + + /** + * Sets the token to be used when authenticating HTTPS requests. + * + * @param token + * A String that represents the access token to be used when authenticating HTTPS requests. + */ + public synchronized void updateToken(final String token) { + this.token = token; + } + + /** + * Gets the account name. + * + * @return A String that contains the account name. + */ + @Override + public String getAccountName() { + return this.accountName; + } + + /** + * Returns a String that represents this instance, optionally including sensitive data. + * + * @param exportSecrets + * true to include sensitive data in the return string; otherwise, false. + * + * @return A String that represents this object, optionally including sensitive data. + */ + @Override + public String toString(final boolean exportSecrets) { + return String.format("%s=%s", CloudStorageAccount.ACCOUNT_TOKEN_NAME, exportSecrets ? this.token + : "[token hidden]"); + } + + @Override + public URI transformUri(URI resourceUri, OperationContext opContext) { + return resourceUri; + } + + @Override + public StorageUri transformUri(StorageUri resourceUri, OperationContext opContext) { + return resourceUri; + } +} diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageCredentialsHelper.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageCredentialsHelper.java index 27ef11fd4..76dcfa571 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageCredentialsHelper.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/core/StorageCredentialsHelper.java @@ -17,12 +17,9 @@ import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; -import com.microsoft.azure.storage.Constants; -import com.microsoft.azure.storage.OperationContext; -import com.microsoft.azure.storage.StorageCredentials; -import com.microsoft.azure.storage.StorageCredentialsAccountAndKey; -import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; -import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.*; + +import javax.net.ssl.HttpsURLConnection; /** * RESERVED FOR INTERNAL USE. A helper method for StorageCredentials. @@ -49,7 +46,8 @@ public static boolean canCredentialsSignRequest(final StorageCredentials creds) * credentials; otherwise, false */ public static boolean canCredentialsGenerateClient(final StorageCredentials creds) { - return canCredentialsSignRequest(creds) || creds.getClass().equals(StorageCredentialsSharedAccessSignature.class); + return canCredentialsSignRequest(creds) || creds.getClass().equals(StorageCredentialsSharedAccessSignature.class) || + creds.getClass().equals(StorageCredentialsToken.class); } /** @@ -80,7 +78,7 @@ public static synchronized String computeHmac256(final StorageCredentials creds, } /** - * Signs a request using the specified operation context under the Shared Key authentication scheme. + * Signs a request using the specified operation context under either the Shared Key or Token authentication scheme. * * @param request * An HttpURLConnection object that represents the request to sign. @@ -113,6 +111,15 @@ public static void signBlobQueueAndFileRequest(final StorageCredentials creds, request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION, String.format("%s %s:%s", "SharedKey", creds.getAccountName(), computedBase64Signature)); + } else if (creds.getClass().equals(StorageCredentialsToken.class)) { + // the token is set as a header to authenticate the HTTPS requests + if (request instanceof HttpsURLConnection) { + request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION, + String.format("%s %s", Constants.HeaderConstants.BEARER, ((StorageCredentialsToken)creds).getToken())); + } + else { + throw new IllegalArgumentException("Token credential is only supported for HTTPS requests."); + } } } diff --git a/pom.xml b/pom.xml index 16660f622..184c6e9fd 100644 --- a/pom.xml +++ b/pom.xml @@ -81,6 +81,12 @@ 1.0.0 test + + com.microsoft.azure + adal4j + 1.4.0 + test + From 3c7758584a2b77adb50e60826ee8f798b17f4dff Mon Sep 17 00:00:00 2001 From: rickle-msft Date: Tue, 22 May 2018 14:04:12 -0700 Subject: [PATCH 3/3] Updated Changelog date and version number in pom --- ChangeLog.txt | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 5d20297ed..4f618ef51 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,4 +1,4 @@ -2018.05.07 Version 7.1.0 +2018.05.22 Version 7.1.0 * Support for 2017-11-09 REST version. Please see our REST API documentation and blogs for information about the related added features. * Added OAuth token support for authentication with HTTPS requests. * Support for write-once read-many containers. diff --git a/pom.xml b/pom.xml index 184c6e9fd..43cdc1bda 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 4.0.0 com.microsoft.azure azure-storage - 7.0.0 + 7.1.0-Preview jar Microsoft Azure Storage Client SDK