Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for generating SAS tokens at the account and Table service level. #21944

Merged
merged 25 commits into from
Jun 4, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e87fe2d
Added support for generating SAS tokens at the Account and Table Serv…
vcolin7 May 29, 2021
d4188f1
Added partition key and row key values for SAS generation.
vcolin7 May 29, 2021
0872d7e
Fixed CheckStyle issues.
vcolin7 May 29, 2021
5e401ed
Fixed SpotBugs issue.
vcolin7 May 29, 2021
aa9ec31
Removed more unused imports.
vcolin7 May 29, 2021
4144ec2
Renamed classes used for generating table-level SAS tokens. Made clie…
vcolin7 Jun 2, 2021
4e85675
Made client builders throw an IllegalStateException if more than one …
vcolin7 Jun 2, 2021
fa7eb98
Changed module-info.java to export the tables package to all other pa…
vcolin7 Jun 2, 2021
159c76a
Added tests for SAS models.
vcolin7 Jun 2, 2021
7578f7c
Added builder tests for when multiple forms of authentication are set.
vcolin7 Jun 2, 2021
a0dc7bc
Updated builders to throw when no endpoint or form of authentication …
vcolin7 Jun 2, 2021
385656a
Fixed CheckStyle issues.
vcolin7 Jun 2, 2021
62ec3ce
Fixed test name.
vcolin7 Jun 2, 2021
475bd88
Removed unnecessary exports for implementation packages in module-inf…
vcolin7 Jun 2, 2021
cd25daa
Applied PR feedback:
vcolin7 Jun 4, 2021
0d9820d
Added tests and renamed test classes to match clients and builders.
vcolin7 Jun 4, 2021
08e581d
Updated CHANGELOG and client builders' JavaDoc.
vcolin7 Jun 4, 2021
fc1cdf3
Applied APIView feedback.
vcolin7 Jun 4, 2021
893da5f
Updated CHANGELOG again.
vcolin7 Jun 4, 2021
d52aa73
Removed unused imports. Simplified SAS token comparison logic.
vcolin7 Jun 4, 2021
594bac2
Fixed SAS token generation at the table level. Re-ordered query param…
vcolin7 Jun 4, 2021
91786bb
Updated CHANGELOG.
vcolin7 Jun 4, 2021
44d20a2
Fixed test and CheckStyle issues.
vcolin7 Jun 4, 2021
096a492
Added @Immutable and @Fluent annotations where appropriate. Made more…
vcolin7 Jun 4, 2021
576b106
Added more @Immutable annotations.
vcolin7 Jun 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdk/tables/azure-data-tables/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### New Features

- Introduced the `TableTransactionAction` class and the `TableTransactionActionType` enum.
- Added support for generating SAS tokens at the Account and Table Service in all clients.

### Breaking Changes

Expand All @@ -22,6 +23,7 @@
- `updateEntity(TableEntity entity, TableEntityUpdateMode updateMode,
boolean ifUnchanged)`
- `getEntity(String partitionKey, String rowKey, List<String> select)`
- Using any of `credential(AzureNamedKeyCredential)`, `credential(AzureSasCredential)` and `sasToken(String)` in client builders now overrides configuration set by the remaining two methods.
vcolin7 marked this conversation as resolved.
Show resolved Hide resolved

## 12.0.0-beta.7 (2021-05-15)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

final class BuilderHelper {
private static final Map<String, String> PROPERTIES =
Expand All @@ -51,8 +48,6 @@ static HttpPipeline buildPipeline(
retryPolicy = (retryPolicy == null) ? new RetryPolicy() : retryPolicy;
logOptions = (logOptions == null) ? new HttpLogOptions() : logOptions;

validateSingleCredentialIsPresent(azureNamedKeyCredential, azureSasCredential, sasToken, logger);

// Closest to API goes first, closest to wire goes last.
List<HttpPipelinePolicy> policies = new ArrayList<>();

Expand Down Expand Up @@ -120,18 +115,4 @@ static HttpPipeline buildNullClientPipeline() {
.httpClient(new NullHttpClient())
.build();
}

private static void validateSingleCredentialIsPresent(AzureNamedKeyCredential azureNamedKeyCredential,
AzureSasCredential azureSasCredential, String sasToken,
ClientLogger logger) {
List<Object> usedCredentials = Stream.of(azureNamedKeyCredential, azureSasCredential, sasToken)
.filter(Objects::nonNull).collect(Collectors.toList());
if (usedCredentials.size() > 1) {
throw logger.logExceptionAsError(new IllegalStateException(
"Only one credential should be used. Credentials present: "
+ usedCredentials.stream().map(c -> c instanceof String ? "sasToken" : c.getClass().getName())
.collect(Collectors.joining(","))
));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.azure.core.annotation.ReturnType;
import com.azure.core.annotation.ServiceClient;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.credential.AzureNamedKeyCredential;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpPipeline;
import com.azure.core.http.HttpRequest;
Expand All @@ -21,15 +22,12 @@
import com.azure.core.util.serializer.SerializerAdapter;
import com.azure.data.tables.implementation.AzureTableImpl;
import com.azure.data.tables.implementation.AzureTableImplBuilder;
import com.azure.data.tables.implementation.TransactionalBatchImpl;
import com.azure.data.tables.implementation.ModelHelper;
import com.azure.data.tables.implementation.TableSasUtils;
import com.azure.data.tables.implementation.TableServiceSasGenerator;
import com.azure.data.tables.implementation.TableUtils;
import com.azure.data.tables.implementation.TransactionalBatchImpl;
import com.azure.data.tables.implementation.models.AccessPolicy;
import com.azure.data.tables.implementation.models.TransactionalBatchChangeSet;
import com.azure.data.tables.implementation.models.TransactionalBatchAction;
import com.azure.data.tables.implementation.models.TransactionalBatchRequestBody;
import com.azure.data.tables.implementation.models.TransactionalBatchSubRequest;
import com.azure.data.tables.implementation.models.TransactionalBatchResponse;
import com.azure.data.tables.implementation.models.OdataMetadataFormat;
import com.azure.data.tables.implementation.models.QueryOptions;
import com.azure.data.tables.implementation.models.ResponseFormat;
Expand All @@ -38,7 +36,11 @@
import com.azure.data.tables.implementation.models.TableProperties;
import com.azure.data.tables.implementation.models.TableResponseProperties;
import com.azure.data.tables.implementation.models.TableServiceError;
import com.azure.data.tables.models.TableTransactionActionResponse;
import com.azure.data.tables.implementation.models.TransactionalBatchAction;
import com.azure.data.tables.implementation.models.TransactionalBatchChangeSet;
import com.azure.data.tables.implementation.models.TransactionalBatchRequestBody;
import com.azure.data.tables.implementation.models.TransactionalBatchResponse;
import com.azure.data.tables.implementation.models.TransactionalBatchSubRequest;
import com.azure.data.tables.models.ListEntitiesOptions;
import com.azure.data.tables.models.TableAccessPolicy;
import com.azure.data.tables.models.TableEntity;
Expand All @@ -47,8 +49,10 @@
import com.azure.data.tables.models.TableServiceException;
import com.azure.data.tables.models.TableSignedIdentifier;
import com.azure.data.tables.models.TableTransactionAction;
import com.azure.data.tables.models.TableTransactionActionResponse;
import com.azure.data.tables.models.TableTransactionFailedException;
import com.azure.data.tables.models.TableTransactionResult;
import com.azure.data.tables.sas.TableServiceSasSignatureValues;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand Down Expand Up @@ -195,6 +199,36 @@ public TableServiceVersion getServiceVersion() {
return TableServiceVersion.fromString(tablesImplementation.getVersion());
}

/**
* Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}.
*
* <p>Note : The client must be authenticated via {@link AzureNamedKeyCredential}
vcolin7 marked this conversation as resolved.
Show resolved Hide resolved
* <p>See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.</p>
*
* @param tableServiceSasSignatureValues {@link TableServiceSasSignatureValues}
*
* @return A {@code String} representing the SAS query parameters.
*/
public String generateSas(TableServiceSasSignatureValues tableServiceSasSignatureValues) {
return generateSas(tableServiceSasSignatureValues, Context.NONE);
}

/**
* Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}.
*
* <p>Note : The client must be authenticated via {@link AzureNamedKeyCredential}
* <p>See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.</p>
*
* @param tableServiceSasSignatureValues {@link TableServiceSasSignatureValues}
* @param context Additional context that is passed through the code when generating a SAS.
*
* @return A {@code String} representing the SAS query parameters.
*/
public String generateSas(TableServiceSasSignatureValues tableServiceSasSignatureValues, Context context) {
vcolin7 marked this conversation as resolved.
Show resolved Hide resolved
return new TableServiceSasGenerator(tableServiceSasSignatureValues, getTableName())
.generateSas(TableSasUtils.extractNamedKeyCredential(getHttpPipeline()), context);
}

/**
* Creates the table within the Tables service.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import java.util.Locale;
import java.util.Map;

import static com.azure.data.tables.implementation.TableUtils.computeHMac256;
import static com.azure.data.tables.implementation.TableSasUtils.computeHMac256;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't add a comment to line 27, so I'll add it here.

Any reason SharedKeyLite is being used over SharedKey? Is there plans on supporting SharedKey as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I wrote this class based off of the SharedKeyCredentialPolicy we used to have in Tables, which was using SharedKeyLite only. I will check if the service indeed supports SharedKey.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears the service only supports SharedKeyLite and that currently there are no plans to support SharedKey.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me correct that. The service supports both SharedKey and SharedKeyLite but the team thinks it's indifferent to use either at this stage.

import static com.azure.data.tables.implementation.TableUtils.parseQueryStringSplitValues;

/**
Expand All @@ -41,21 +41,24 @@ public TableAzureNamedKeyCredentialPolicy(AzureNamedKeyCredential credential) {
*
* @param context The context of the request.
* @param next The next policy in the pipeline.
*
* @return A reactive result containing the HTTP response.
*/
public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
String authorizationValue = generateAuthorizationHeader(context.getHttpRequest().getUrl(),
context.getHttpRequest().getHeaders().toMap());
context.getHttpRequest().setHeader("Authorization", authorizationValue);

return next.process();
}

/**
* Generates the Auth Headers
*
* @param requestUrl the URL which the request is going to
* @param headers the headers of the request
* @return the auth header
* @param requestUrl The URL which the request is going to.
* @param headers The headers of the request.
*
* @return The auth header
*/
String generateAuthorizationHeader(URL requestUrl, Map<String, String> headers) {
String signature = computeHMac256(credential.getAzureNamedKey().getKey(), buildStringToSign(requestUrl,
Expand All @@ -68,6 +71,7 @@ String generateAuthorizationHeader(URL requestUrl, Map<String, String> headers)
*
* @param requestUrl The Url which the request is going to.
* @param headers The headers of the request.
*
* @return A string to sign for the request.
*/
private String buildStringToSign(URL requestUrl, Map<String, String> headers) {
Expand All @@ -87,6 +91,7 @@ private String buildStringToSign(URL requestUrl, Map<String, String> headers) {
*
* @param headers A map of the headers which the request has.
* @param headerName The name of the header to get the standard header for.
*
* @return The standard header for the given name.
*/
private String getStandardHeaderValue(Map<String, String> headers, String headerName) {
Expand All @@ -100,6 +105,7 @@ private String getStandardHeaderValue(Map<String, String> headers, String header
* Returns the canonicalized resource needed for a request.
*
* @param requestUrl The url of the request.
*
* @return The string that is the canonicalized resource.
*/
private String getCanonicalizedResource(URL requestUrl) {
Expand Down Expand Up @@ -133,4 +139,13 @@ private String getCanonicalizedResource(URL requestUrl) {

return canonicalizedResource.toString();
}

/**
* Get the {@link AzureNamedKeyCredential} linked to the policy.
*
* @return The {@link AzureNamedKeyCredential}.
*/
public AzureNamedKeyCredential getCredential() {
return credential;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
import com.azure.core.annotation.ReturnType;
import com.azure.core.annotation.ServiceClient;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.credential.AzureNamedKeyCredential;
import com.azure.core.http.rest.PagedIterable;
import com.azure.core.http.rest.Response;
import com.azure.core.util.Context;
import com.azure.data.tables.models.TableTransactionActionResponse;
import com.azure.data.tables.models.ListEntitiesOptions;
import com.azure.data.tables.models.TableEntity;
import com.azure.data.tables.models.TableEntityUpdateMode;
import com.azure.data.tables.models.TableItem;
import com.azure.data.tables.models.TableServiceException;
import com.azure.data.tables.models.TableSignedIdentifier;
import com.azure.data.tables.models.TableTransactionAction;
import com.azure.data.tables.models.TableTransactionActionResponse;
import com.azure.data.tables.models.TableTransactionFailedException;
import com.azure.data.tables.models.TableTransactionResult;
import com.azure.data.tables.sas.TableServiceSasSignatureValues;

import java.time.Duration;
import java.util.List;
Expand Down Expand Up @@ -80,6 +82,35 @@ public TableServiceVersion getServiceVersion() {
return this.client.getServiceVersion();
}

/**
* Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}.
*
* <p>Note : The client must be authenticated via {@link AzureNamedKeyCredential}
* <p>See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.</p>
*
* @param tableServiceSasSignatureValues {@link TableServiceSasSignatureValues}
*
* @return A {@code String} representing the SAS query parameters.
*/
public String generateSas(TableServiceSasSignatureValues tableServiceSasSignatureValues) {
return client.generateSas(tableServiceSasSignatureValues, Context.NONE);
}

/**
* Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}.
*
* <p>Note : The client must be authenticated via {@link AzureNamedKeyCredential}
* <p>See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.</p>
*
* @param tableServiceSasSignatureValues {@link TableServiceSasSignatureValues}
* @param context Additional context that is passed through the code when generating a SAS.
*
* @return A {@code String} representing the SAS query parameters.
*/
public String generateSas(TableServiceSasSignatureValues tableServiceSasSignatureValues, Context context) {
return client.generateSas(tableServiceSasSignatureValues, context);
}

/**
* Creates the table within the Tables service.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ public TableClientBuilder configuration(Configuration configuration) {
}

/**
* Sets the SAS token used to authorize requests sent to the service.
* Sets the SAS token used to authorize requests sent to the service. Setting this value will undo other
* configuration made with {@code credential(AzureSasCredential)} or {@code credential(AzureNamedKeyCredential)}.
*
* @param sasToken The SAS token to use for authenticating requests.
*
Expand All @@ -204,12 +205,14 @@ public TableClientBuilder sasToken(String sasToken) {

this.sasToken = sasToken;
this.azureNamedKeyCredential = null;
this.azureSasCredential = null;

return this;
}

/**
* Sets the {@link AzureSasCredential} used to authorize requests sent to the service.
* Sets the {@link AzureSasCredential} used to authorize requests sent to the service. Setting this value will undo
* other configuration made with {@code credential(AzureNamedKeyCredential)} or {@code sasToken(String)}.
*
* @param credential {@link AzureSasCredential} used to authorize requests sent to the service.
*
Expand All @@ -223,12 +226,15 @@ public TableClientBuilder credential(AzureSasCredential credential) {
}

this.azureSasCredential = credential;
this.azureNamedKeyCredential = null;
this.sasToken = null;

return this;
}

/**
* Sets the {@link AzureNamedKeyCredential} used to authorize requests sent to the service.
* Sets the {@link AzureNamedKeyCredential} used to authorize requests sent to the service. Setting this will
* override any other configuration made with {@code credential(AzureSasCredential)} or {@code sasToken(String)}.
*
* @param credential {@link AzureNamedKeyCredential} used to authorize requests sent to the service.
*
Expand All @@ -242,6 +248,7 @@ public TableClientBuilder credential(AzureNamedKeyCredential credential) {
}

this.azureNamedKeyCredential = credential;
this.azureSasCredential = null;
this.sasToken = null;

return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.azure.core.annotation.ReturnType;
import com.azure.core.annotation.ServiceClient;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.credential.AzureNamedKeyCredential;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpPipeline;
import com.azure.core.http.HttpRequest;
Expand All @@ -21,6 +22,8 @@
import com.azure.data.tables.implementation.AzureTableImpl;
import com.azure.data.tables.implementation.AzureTableImplBuilder;
import com.azure.data.tables.implementation.ModelHelper;
import com.azure.data.tables.implementation.TableAccountSasGenerator;
import com.azure.data.tables.implementation.TableSasUtils;
import com.azure.data.tables.implementation.TableUtils;
import com.azure.data.tables.implementation.models.CorsRule;
import com.azure.data.tables.implementation.models.GeoReplication;
Expand All @@ -45,6 +48,7 @@
import com.azure.data.tables.models.TableServiceProperties;
import com.azure.data.tables.models.TableServiceRetentionPolicy;
import com.azure.data.tables.models.TableServiceStatistics;
import com.azure.data.tables.sas.TableAccountSasSignatureValues;
import reactor.core.publisher.Mono;

import java.net.URI;
Expand Down Expand Up @@ -147,6 +151,38 @@ public TableServiceVersion getServiceVersion() {
return TableServiceVersion.fromString(implementation.getVersion());
}

/**
* Generates an account SAS for the Azure Storage account using the specified
* {@link TableAccountSasSignatureValues}.
*
* <p>Note : The client must be authenticated via {@link AzureNamedKeyCredential}.
* <p>See {@link TableAccountSasSignatureValues} for more information on how to construct an account SAS.</p>
*
* @param tableAccountSasSignatureValues {@link TableAccountSasSignatureValues}.
*
* @return A {@link String} representing the SAS query parameters.
*/
public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues) {
return generateAccountSas(tableAccountSasSignatureValues, Context.NONE);
}

/**
* Generates an account SAS for the Azure Storage account using the specified
* {@link TableAccountSasSignatureValues}.
*
* <p>Note : The client must be authenticated via {@link AzureNamedKeyCredential}.
* <p>See {@link TableAccountSasSignatureValues} for more information on how to construct an account SAS.</p>
*
* @param tableAccountSasSignatureValues {@link TableAccountSasSignatureValues}.
* @param context Additional context that is passed through the code when generating a SAS.
*
* @return A {@link String} representing the SAS query parameters.
*/
public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues, Context context) {
return new TableAccountSasGenerator(tableAccountSasSignatureValues)
.generateSas(TableSasUtils.extractNamedKeyCredential(getHttpPipeline()), context);
}

/**
* Gets a {@link TableAsyncClient} instance for the provided table in the account.
*
Expand Down
Loading