Skip to content
This repository was archived by the owner on Jul 19, 2024. It is now read-only.

Commit a36aaa5

Browse files
zezha-msftrickle-msft
authored andcommitted
Added support for token authentication for HTTPS requests
1 parent f27493d commit a36aaa5

File tree

11 files changed

+303
-12
lines changed

11 files changed

+303
-12
lines changed

ChangeLog.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
XXXX.XX.XX Version X.X.X
1+
2018.05.07 Version 7.1.0
2+
* Support for 2017-11-09 REST version. Please see our REST API documentation and blogs for information about the related added features.
3+
* Added OAuth token support for authentication with HTTPS requests.
24
* Support for write-once read-many containers.
35

46
2018.02.05 Version 7.0.0

microsoft-azure-storage-test/res/TestConfigurations.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
<BlobServiceSecondaryEndpoint>http://[ACCOUNT]-secondary.blob.core.windows.net</BlobServiceSecondaryEndpoint>
2727
<QueueServiceSecondaryEndpoint>http://[ACCOUNT]-secondary.queue.core.windows.net</QueueServiceSecondaryEndpoint>
2828
<TableServiceSecondaryEndpoint>http://[ACCOUNT]-secondary.table.core.windows.net</TableServiceSecondaryEndpoint>
29+
<ActiveDirectoryApplicationId>[APPLICATION_ID]</ActiveDirectoryApplicationId>
30+
<ActiveDirectoryApplicationSecret>[APPLICATION_SECRET]</ActiveDirectoryApplicationSecret>
31+
<ActiveDirectoryTenantId>[TENANT_ID]</ActiveDirectoryTenantId>
2932
</TenantConfiguration>
3033
</TenantConfigurations>
3134
</TestConfigurations>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.microsoft.azure.storage;
2+
3+
import com.microsoft.azure.storage.blob.CloudBlobClient;
4+
import com.microsoft.azure.storage.queue.CloudQueueClient;
5+
import org.junit.Test;
6+
import org.junit.experimental.categories.Category;
7+
8+
import javax.naming.ServiceUnavailableException;
9+
import java.net.MalformedURLException;
10+
import java.net.URISyntaxException;
11+
import java.util.concurrent.ExecutionException;
12+
13+
import static org.junit.Assert.assertEquals;
14+
import static org.junit.Assert.fail;
15+
16+
/**
17+
* OAuth tests.
18+
*/
19+
public class OAuthTests {
20+
/**
21+
* Test OAuth with respect to blob client
22+
*/
23+
@Test
24+
@Category({ TestRunners.DevFabricTests.class, TestRunners.DevStoreTests.class, TestRunners.CloudTests.class })
25+
public void testOAuthTokenWithBlobClient() throws StorageException, URISyntaxException, MalformedURLException, InterruptedException, ExecutionException, ServiceUnavailableException {
26+
// get the standard test account
27+
CloudStorageAccount account = TestHelper.getAccount();
28+
29+
// create a token credential and replace the account's credential
30+
StorageCredentialsToken storageCredentialsToken = new StorageCredentialsToken(TestHelper.getAccountName(), TestHelper.generateOAuthToken());
31+
account.setCredentials(storageCredentialsToken);
32+
33+
// test to make sure authentication is working
34+
CloudBlobClient blobClient = account.createCloudBlobClient();
35+
blobClient.downloadServiceProperties();
36+
37+
// change the token to see it fail
38+
try {
39+
storageCredentialsToken.updateToken("BLA");
40+
blobClient.downloadServiceProperties();
41+
fail();
42+
} catch (StorageException e) {
43+
assertEquals("AuthenticationFailed", e.getErrorCode());
44+
}
45+
46+
// change the token again to see it succeed
47+
storageCredentialsToken.updateToken(TestHelper.generateOAuthToken());
48+
blobClient.downloadServiceProperties();
49+
}
50+
51+
/**
52+
* Test OAuth with respect to queue client
53+
*/
54+
@Test
55+
@Category({ TestRunners.DevFabricTests.class, TestRunners.DevStoreTests.class, TestRunners.CloudTests.class })
56+
public void testOAuthTokenWithQueueClient() throws StorageException, URISyntaxException, MalformedURLException, InterruptedException, ExecutionException, ServiceUnavailableException {
57+
// get the standard test account
58+
CloudStorageAccount account = TestHelper.getAccount();
59+
60+
// create a token credential and replace the account's credential
61+
StorageCredentialsToken storageCredentialsToken = new StorageCredentialsToken(TestHelper.getAccountName(), TestHelper.generateOAuthToken());
62+
account.setCredentials(storageCredentialsToken);
63+
64+
// test to make sure authentication is working
65+
CloudQueueClient queueClient = account.createCloudQueueClient();
66+
queueClient.downloadServiceProperties();
67+
68+
// change the token to see it fail
69+
try {
70+
storageCredentialsToken.updateToken("BLA");
71+
queueClient.downloadServiceProperties();
72+
fail();
73+
} catch (StorageException e) {
74+
assertEquals("AuthenticationFailed", e.getErrorCode());
75+
}
76+
77+
// change the token again to see it succeed
78+
storageCredentialsToken.updateToken(TestHelper.generateOAuthToken());
79+
queueClient.downloadServiceProperties();
80+
}
81+
}

microsoft-azure-storage-test/src/com/microsoft/azure/storage/Tenant.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public class Tenant {
3232
private Integer fileHttpsPortOverride = null;
3333
private Integer queueHttpsPortOverride = null;
3434
private Integer tableHttpsPortOverride = null;
35+
private String activeDirectoryApplicationId;
36+
private String activeDirectoryApplicationSecret;
37+
private String activeDirectoryTenantId;
3538

3639
public String getTenantName() {
3740
return this.tenantName;
@@ -92,7 +95,31 @@ public Integer getQueueHttpsPortOverride() {
9295
public Integer getTableHttpsPortOverride() {
9396
return this.tableHttpsPortOverride;
9497
}
95-
98+
99+
public String getActiveDirectoryApplicationId() {
100+
return this.activeDirectoryApplicationId;
101+
}
102+
103+
public String getActiveDirectoryApplicationSecret() {
104+
return this.activeDirectoryApplicationSecret;
105+
}
106+
107+
public String getActiveDirectoryTenantId() {
108+
return this.activeDirectoryTenantId;
109+
}
110+
111+
public void setActiveDirectoryApplicationId(String activeDirectoryApplicationId) {
112+
this.activeDirectoryApplicationId = activeDirectoryApplicationId;
113+
}
114+
115+
public void setActiveDirectoryApplicationSecret(String activeDirectoryApplicationSecret) {
116+
this.activeDirectoryApplicationSecret = activeDirectoryApplicationSecret;
117+
}
118+
119+
public void setActiveDirectoryTenantId(String activeDirectoryTenantId) {
120+
this.activeDirectoryTenantId = activeDirectoryTenantId;
121+
}
122+
96123
void setTenantName(String tenantName) {
97124
this.tenantName = tenantName;
98125
}

microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.File;
2222
import java.io.IOException;
2323
import java.io.InputStream;
24+
import java.net.MalformedURLException;
2425
import java.net.URI;
2526
import java.net.URISyntaxException;
2627
import java.net.URL;
@@ -32,14 +33,22 @@
3233
import java.util.ArrayList;
3334
import java.util.Arrays;
3435
import java.util.Random;
36+
import java.util.concurrent.ExecutionException;
37+
import java.util.concurrent.ExecutorService;
38+
import java.util.concurrent.Executors;
39+
import java.util.concurrent.Future;
3540

3641
import javax.crypto.KeyGenerator;
3742
import javax.crypto.NoSuchPaddingException;
3843
import javax.crypto.SecretKey;
44+
import javax.naming.ServiceUnavailableException;
3945
import javax.xml.parsers.DocumentBuilder;
4046
import javax.xml.parsers.DocumentBuilderFactory;
4147
import javax.xml.parsers.ParserConfigurationException;
4248

49+
import com.microsoft.aad.adal4j.AuthenticationContext;
50+
import com.microsoft.aad.adal4j.AuthenticationResult;
51+
import com.microsoft.aad.adal4j.ClientCredential;
4352
import org.junit.AssumptionViolatedException;
4453
import org.w3c.dom.DOMException;
4554
import org.w3c.dom.Document;
@@ -67,6 +76,47 @@ public class TestHelper {
6776
private final static boolean enableFiddler = false;
6877
private final static boolean requireSecondaryEndpoint = false;
6978

79+
public static String generateOAuthToken() throws MalformedURLException, InterruptedException, ExecutionException, ServiceUnavailableException, StorageException {
80+
// if the test configuration has not been read in yet, trigger a getAccount to read in the information
81+
if(tenant == null) {
82+
getAccount();
83+
}
84+
85+
// this boiler plate code is from the ADAL sample
86+
String authority = String.format("https://login.microsoftonline.com/%s/oauth2/token",
87+
tenant.getActiveDirectoryTenantId());
88+
ClientCredential credential = new ClientCredential(tenant.getActiveDirectoryApplicationId(),
89+
tenant.getActiveDirectoryApplicationSecret());
90+
91+
ExecutorService service = null;
92+
AuthenticationResult result;
93+
94+
try {
95+
service = Executors.newFixedThreadPool(1);
96+
AuthenticationContext context = new AuthenticationContext(authority, true, service);
97+
Future<AuthenticationResult> future = context.acquireToken("https://storage.azure.com", credential, null);
98+
result = future.get();
99+
} finally {
100+
if(service != null) {
101+
service.shutdown();
102+
}
103+
}
104+
105+
if (result == null) {
106+
throw new ServiceUnavailableException("authentication result was null");
107+
}
108+
return result.getAccessToken();
109+
}
110+
111+
public static String getAccountName() throws StorageException {
112+
// if the test configuration has not been read in yet, trigger a getAccount to read in the information
113+
if(tenant == null) {
114+
getAccount();
115+
}
116+
117+
return tenant.getAccountName();
118+
}
119+
70120
public static CloudBlobClient createCloudBlobClient() throws StorageException {
71121
CloudBlobClient client = getAccount().createCloudBlobClient();
72122
return client;
@@ -305,7 +355,7 @@ public static void verifyServiceStats(ServiceStats stats) {
305355
}
306356
}
307357

308-
private static CloudStorageAccount getAccount() throws StorageException {
358+
public static CloudStorageAccount getAccount() throws StorageException {
309359
// Only do this the first time TestBase is called as storage account is static
310360
if (account == null) {
311361
//enable fiddler
@@ -502,6 +552,15 @@ else if (name.equals("TableHttpsPortOverride")) {
502552
else if (name.equals("FileHttpsPortOverride")) {
503553
tenant.setFileHttpsPortOverride(Integer.parseInt(node.getTextContent()));
504554
}
555+
else if (name.equals("ActiveDirectoryApplicationId")) {
556+
tenant.setActiveDirectoryApplicationId(node.getTextContent());
557+
}
558+
else if (name.equals("ActiveDirectoryApplicationSecret")) {
559+
tenant.setActiveDirectoryApplicationSecret(node.getTextContent());
560+
}
561+
else if (name.equals("ActiveDirectoryTenantId")) {
562+
tenant.setActiveDirectoryTenantId(node.getTextContent());
563+
}
505564
else if (!premiumBlob){
506565
throw new IllegalArgumentException(String.format(
507566
"Invalid child of TenantConfiguration with name: %s", name));

microsoft-azure-storage/src/com/microsoft/azure/storage/CloudStorageAccount.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ public final class CloudStorageAccount {
4646
*/
4747
protected static final String ACCOUNT_KEY_NAME = "AccountKey";
4848

49+
/**
50+
* Represents the setting name for the token credential.
51+
*/
52+
protected static final String ACCOUNT_TOKEN_NAME = "AccountToken";
53+
4954
/**
5055
* Represents the setting name for the account name.
5156
*/

microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,11 @@ public static class HeaderConstants {
314314
*/
315315
public static final String AUTHORIZATION = "Authorization";
316316

317+
/**
318+
* The keyword used for bearer token authorization.
319+
*/
320+
public static final String BEARER = "Bearer";
321+
317322
/**
318323
* The format string for specifying ranges with only begin offset.
319324
*/

microsoft-azure-storage/src/com/microsoft/azure/storage/StorageCredentials.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
/**
2727
* Represents a set of credentials used to authenticate access to a Microsoft Azure storage account. This is the base
28-
* class for the {@link StorageCredentialsAccountAndKey} and {@link StorageCredentialsSharedAccessSignature} classes.
28+
* class for the {@link StorageCredentialsAccountAndKey}, {@link StorageCredentialsToken}, and {@link StorageCredentialsSharedAccessSignature} classes.
2929
*/
3030
public abstract class StorageCredentials {
3131

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.microsoft.azure.storage;
2+
3+
import java.net.URI;
4+
5+
/**
6+
* Represents storage account credentials, based on an authentication token, for accessing the Microsoft Azure
7+
* storage services.
8+
*/
9+
public final class StorageCredentialsToken extends StorageCredentials{
10+
/**
11+
* Stores the token for the credentials.
12+
*/
13+
private volatile String token;
14+
15+
/**
16+
* Stores the account name.
17+
*/
18+
private volatile String accountName;
19+
20+
/**
21+
* Creates an instance of the <code>StorageCredentialsToken</code> class, using the specified token.
22+
* Token credentials must only be used with HTTPS requests on the blob and queue services.
23+
* The specified token is stored as a <code>String</code>.
24+
*
25+
* @param token
26+
* A <code>String</code> that represents the token.
27+
*/
28+
public StorageCredentialsToken(String accountName, String token) {
29+
this.accountName = accountName;
30+
this.token = token;
31+
}
32+
33+
/**
34+
* Gets whether this <code>StorageCredentials</code> object only allows access via HTTPS.
35+
*
36+
* @return A <code>boolean</code> representing whether this <code>StorageCredentials</code>
37+
* object only allows access via HTTPS.
38+
*/
39+
@Override
40+
public boolean isHttpsOnly() {
41+
return true;
42+
}
43+
44+
/**
45+
* Gets the token.
46+
*
47+
* @return A <code>String</code> that contains the token.
48+
*/
49+
public String getToken() {
50+
return this.token;
51+
}
52+
53+
/**
54+
* Sets the token to be used when authenticating HTTPS requests.
55+
*
56+
* @param token
57+
* A <code>String</code> that represents the access token to be used when authenticating HTTPS requests.
58+
*/
59+
public synchronized void updateToken(final String token) {
60+
this.token = token;
61+
}
62+
63+
/**
64+
* Gets the account name.
65+
*
66+
* @return A <code>String</code> that contains the account name.
67+
*/
68+
@Override
69+
public String getAccountName() {
70+
return this.accountName;
71+
}
72+
73+
/**
74+
* Returns a <code>String</code> that represents this instance, optionally including sensitive data.
75+
*
76+
* @param exportSecrets
77+
* <code>true</code> to include sensitive data in the return string; otherwise, <code>false</code>.
78+
*
79+
* @return A <code>String</code> that represents this object, optionally including sensitive data.
80+
*/
81+
@Override
82+
public String toString(final boolean exportSecrets) {
83+
return String.format("%s=%s", CloudStorageAccount.ACCOUNT_TOKEN_NAME, exportSecrets ? this.token
84+
: "[token hidden]");
85+
}
86+
87+
@Override
88+
public URI transformUri(URI resourceUri, OperationContext opContext) {
89+
return resourceUri;
90+
}
91+
92+
@Override
93+
public StorageUri transformUri(StorageUri resourceUri, OperationContext opContext) {
94+
return resourceUri;
95+
}
96+
}

0 commit comments

Comments
 (0)