From e87fe2d4a316ef90f2be3a00862add58a7e8f657 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 28 May 2021 20:21:06 -0500 Subject: [PATCH 01/25] Added support for generating SAS tokens at the Account and Table Service in all clients. Updated CHANGELOG. --- sdk/tables/azure-data-tables/CHANGELOG.md | 2 + .../com/azure/data/tables/BuilderHelper.java | 19 - .../azure/data/tables/TableAsyncClient.java | 48 +- .../TableAzureNamedKeyCredentialPolicy.java | 23 +- .../com/azure/data/tables/TableClient.java | 33 +- .../azure/data/tables/TableClientBuilder.java | 13 +- .../data/tables/TableServiceAsyncClient.java | 36 ++ .../azure/data/tables/TableServiceClient.java | 33 ++ .../tables/TableServiceClientBuilder.java | 13 +- .../implementation/StorageConstants.java | 6 +- .../TableAccountSasGenerator.java | 114 +++++ .../tables/implementation/TableSasUtils.java | 114 +++++ .../TableServiceSasGenerator.java | 174 ++++++++ .../tables/implementation/TableUtils.java | 210 +++++++-- .../tables/sas/TableAccountSasPermission.java | 409 ++++++++++++++++++ .../sas/TableAccountSasResourceType.java | 161 +++++++ .../tables/sas/TableAccountSasService.java | 173 ++++++++ .../sas/TableAccountSasSignatureValues.java | 198 +++++++++ .../data/tables/sas/TableSasIpRange.java | 87 ++++ .../TableSasProtocol.java} | 17 +- .../tables/sas/TableServiceSasPermission.java | 209 +++++++++ .../sas/TableServiceSasSignatureValues.java | 312 +++++++++++++ .../src/main/java/module-info.java | 2 + 23 files changed, 2330 insertions(+), 76 deletions(-) create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java rename sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/{implementation/SasProtocol.java => sas/TableSasProtocol.java} (74%) create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasPermission.java create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasSignatureValues.java diff --git a/sdk/tables/azure-data-tables/CHANGELOG.md b/sdk/tables/azure-data-tables/CHANGELOG.md index 0412600a6812f..e3e9832f75752 100644 --- a/sdk/tables/azure-data-tables/CHANGELOG.md +++ b/sdk/tables/azure-data-tables/CHANGELOG.md @@ -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 @@ -22,6 +23,7 @@ - `updateEntity(TableEntity entity, TableEntityUpdateMode updateMode, boolean ifUnchanged)` - `getEntity(String partitionKey, String rowKey, List select)` +- Using any of `credential(AzureNamedKeyCredential)`, `credential(AzureSasCredential)` and `sasToken(String)` in client builders now overrides configuration set by the remaining two methods. ## 12.0.0-beta.7 (2021-05-15) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java index 7e6f350f11f34..dbf542de64eb2 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java @@ -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 PROPERTIES = @@ -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 policies = new ArrayList<>(); @@ -120,18 +115,4 @@ static HttpPipeline buildNullClientPipeline() { .httpClient(new NullHttpClient()) .build(); } - - private static void validateSingleCredentialIsPresent(AzureNamedKeyCredential azureNamedKeyCredential, - AzureSasCredential azureSasCredential, String sasToken, - ClientLogger logger) { - List 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(",")) - )); - } - } } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java index fead6b55b6d71..a4f569550832a 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java @@ -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; @@ -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; @@ -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; @@ -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; @@ -195,6 +199,36 @@ public TableServiceVersion getServiceVersion() { return TableServiceVersion.fromString(tablesImplementation.getVersion()); } + /** + * Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}. + * + *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} + *

See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.

+ * + * @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}. + * + *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} + *

See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.

+ * + * @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 new TableServiceSasGenerator(tableServiceSasSignatureValues, getTableName()) + .generateSas(TableSasUtils.extractNamedKeyCredential(getHttpPipeline()), context); + } + /** * Creates the table within the Tables service. * diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAzureNamedKeyCredentialPolicy.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAzureNamedKeyCredentialPolicy.java index 6bddd72130ef0..1c2fe511ebee3 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAzureNamedKeyCredentialPolicy.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAzureNamedKeyCredentialPolicy.java @@ -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; import static com.azure.data.tables.implementation.TableUtils.parseQueryStringSplitValues; /** @@ -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 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 headers) { String signature = computeHMac256(credential.getAzureNamedKey().getKey(), buildStringToSign(requestUrl, @@ -68,6 +71,7 @@ String generateAuthorizationHeader(URL requestUrl, Map 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 headers) { @@ -87,6 +91,7 @@ private String buildStringToSign(URL requestUrl, Map 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 headers, String headerName) { @@ -100,6 +105,7 @@ private String getStandardHeaderValue(Map 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) { @@ -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; + } } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java index f811b2ee05e4f..a8f90f2fded96 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java @@ -5,10 +5,10 @@ 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; @@ -16,8 +16,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 java.time.Duration; import java.util.List; @@ -80,6 +82,35 @@ public TableServiceVersion getServiceVersion() { return this.client.getServiceVersion(); } + /** + * Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}. + * + *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} + *

See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.

+ * + * @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}. + * + *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} + *

See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.

+ * + * @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. * diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java index cda5f3e719659..055caf2de4e59 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java @@ -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. * @@ -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. * @@ -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. * @@ -242,6 +248,7 @@ public TableClientBuilder credential(AzureNamedKeyCredential credential) { } this.azureNamedKeyCredential = credential; + this.azureSasCredential = null; this.sasToken = null; return this; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java index 0eb9d2ff0e6d3..008b7df7bb2d4 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java @@ -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; @@ -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; @@ -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; @@ -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}. + * + *

Note : The client must be authenticated via {@link AzureNamedKeyCredential}. + *

See {@link TableAccountSasSignatureValues} for more information on how to construct an account SAS.

+ * + * @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}. + * + *

Note : The client must be authenticated via {@link AzureNamedKeyCredential}. + *

See {@link TableAccountSasSignatureValues} for more information on how to construct an account SAS.

+ * + * @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. * diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java index e7e85e270cce8..0835771731d8a 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java @@ -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.HttpResponse; import com.azure.core.http.rest.PagedIterable; import com.azure.core.http.rest.Response; @@ -18,6 +19,7 @@ import com.azure.data.tables.models.TableServiceException; import com.azure.data.tables.models.TableServiceProperties; import com.azure.data.tables.models.TableServiceStatistics; +import com.azure.data.tables.sas.TableAccountSasSignatureValues; import reactor.core.publisher.Mono; import java.time.Duration; @@ -71,6 +73,37 @@ public TableServiceVersion getServiceVersion() { return client.getServiceVersion(); } + /** + * Generates an account SAS for the Azure Storage account using the specified + * {@link TableAccountSasSignatureValues}. + * + *

Note : The client must be authenticated via {@link AzureNamedKeyCredential}. + *

See {@link TableAccountSasSignatureValues} for more information on how to construct an account SAS.

+ * + * @param tableAccountSasSignatureValues {@link TableAccountSasSignatureValues}. + * + * @return A {@link String} representing the SAS query parameters. + */ + public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues) { + return client.generateAccountSas(tableAccountSasSignatureValues); + } + + /** + * Generates an account SAS for the Azure Storage account using the specified + * {@link TableAccountSasSignatureValues}. + * + *

Note : The client must be authenticated via {@link AzureNamedKeyCredential}. + *

See {@link TableAccountSasSignatureValues} for more information on how to construct an account SAS.

+ * + * @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 client.generateAccountSas(tableAccountSasSignatureValues, context); + } + /** * Gets a {@link TableClient} instance for the provided table in the account. * diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java index c5a246b0adddd..2de97a5505481 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java @@ -177,7 +177,8 @@ public TableServiceClientBuilder 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. * @@ -196,12 +197,14 @@ public TableServiceClientBuilder 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. * @@ -215,12 +218,15 @@ public TableServiceClientBuilder 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. * @@ -234,6 +240,7 @@ public TableServiceClientBuilder credential(AzureNamedKeyCredential credential) } this.azureNamedKeyCredential = credential; + this.azureSasCredential = null; this.sasToken = null; return this; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java index 9ea5495484535..8cd2afb770ea2 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java @@ -3,6 +3,8 @@ package com.azure.data.tables.implementation; +import com.azure.data.tables.sas.TableSasProtocol; + import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Locale; @@ -36,12 +38,12 @@ private StorageConstants() { public static final long TB = 1024L * GB; /** - * Represents the value for {@link SasProtocol#HTTPS_ONLY}. + * Represents the value for {@link TableSasProtocol#HTTPS_ONLY}. */ public static final String HTTPS = "https"; /** - * Represents the value for {@link SasProtocol#HTTPS_HTTP}. + * Represents the value for {@link TableSasProtocol#HTTPS_HTTP}. */ public static final String HTTPS_HTTP = "https,http"; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java new file mode 100644 index 0000000000000..5405884690a29 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.implementation; + +import com.azure.core.credential.AzureNamedKeyCredential; +import com.azure.core.util.Context; +import com.azure.core.util.CoreUtils; +import com.azure.core.util.logging.ClientLogger; +import com.azure.data.tables.sas.TableAccountSasPermission; +import com.azure.data.tables.sas.TableAccountSasSignatureValues; +import com.azure.data.tables.sas.TableSasIpRange; +import com.azure.data.tables.sas.TableSasProtocol; + +import java.time.OffsetDateTime; +import java.util.Objects; + +import static com.azure.data.tables.implementation.TableSasUtils.computeHMac256; +import static com.azure.data.tables.implementation.TableSasUtils.formatQueryParameterDate; +import static com.azure.data.tables.implementation.TableSasUtils.logStringToSign; +import static com.azure.data.tables.implementation.TableSasUtils.tryAppendQueryParameter; + +/** + * A class containing utility methods for generating SAS tokens for the Azure Storage accounts. + */ +public class TableAccountSasGenerator { + private final ClientLogger logger = new ClientLogger(TableAccountSasGenerator.class); + private String version; + private TableSasProtocol protocol; + private OffsetDateTime startTime; + private OffsetDateTime expiryTime; + private String permissions; + private TableSasIpRange sasIpRange; + private String services; + private String resourceTypes; + + /** + * Creates a new {@link TableAccountSasGenerator} with the specified parameters. + * + * @param sasValues The {@link TableAccountSasSignatureValues account signature values}. + */ + public TableAccountSasGenerator(TableAccountSasSignatureValues sasValues) { + this.version = sasValues.getVersion(); + this.protocol = sasValues.getProtocol(); + this.startTime = sasValues.getStartTime(); + this.expiryTime = sasValues.getExpiryTime(); + this.permissions = sasValues.getPermissions(); + this.sasIpRange = sasValues.getSasIpRange(); + this.services = sasValues.getServices(); + this.resourceTypes = sasValues.getResourceTypes(); + } + + /** + * Generates a Sas signed with a {@link AzureNamedKeyCredential}. + * + * @param azureNamedKeyCredential {@link AzureNamedKeyCredential} + * @param context Additional context that is passed through the code when generating a SAS. + * @return A String representing the Sas + */ + public String generateSas(AzureNamedKeyCredential azureNamedKeyCredential, Context context) { + Objects.requireNonNull(azureNamedKeyCredential, "'azureNamedKeyCredential' cannot be null."); + Objects.requireNonNull(services, "'services' cannot be null."); + Objects.requireNonNull(resourceTypes, "'resourceTypes' cannot be null."); + Objects.requireNonNull(expiryTime, "'expiryTime' cannot be null."); + Objects.requireNonNull(permissions, "'permissions' cannot be null."); + + if (CoreUtils.isNullOrEmpty(version)) { + version = StorageConstants.HeaderConstants.TARGET_STORAGE_VERSION; + } + String stringToSign = stringToSign(azureNamedKeyCredential); + logStringToSign(logger, stringToSign, context); + + // Signature is generated on the un-url-encoded values. + String signature = computeHMac256(azureNamedKeyCredential.getAzureNamedKey().getKey(), stringToSign); + + return encode(signature); + } + + private String stringToSign(final AzureNamedKeyCredential azureNamedKeyCredential) { + return String.join("\n", + azureNamedKeyCredential.getAzureNamedKey().getName(), + TableAccountSasPermission.parse(this.permissions).toString(), // guarantees ordering + this.services, + resourceTypes, + this.startTime == null ? "" : StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), + StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), + this.sasIpRange == null ? "" : this.sasIpRange.toString(), + this.protocol == null ? "" : this.protocol.toString(), + this.version, + "" // Account SAS requires an additional newline character + ); + } + + private String encode(String signature) { + /* + We should be url-encoding each key and each value, but because we know all the keys and values will encode to + themselves, we cheat except for the signature value. + */ + StringBuilder sb = new StringBuilder(); + + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SERVICE_VERSION, this.version); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SERVICES, this.services); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_RESOURCES_TYPES, this.resourceTypes); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_PROTOCOL, this.protocol); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_START_TIME, + formatQueryParameterDate(this.startTime)); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_EXPIRY_TIME, + formatQueryParameterDate(this.expiryTime)); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNATURE, signature); + + return sb.toString(); + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java new file mode 100644 index 0000000000000..9c38fb6859c39 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.implementation; + +import com.azure.core.credential.AzureNamedKeyCredential; +import com.azure.core.http.HttpPipeline; +import com.azure.core.util.Context; +import com.azure.core.util.logging.ClientLogger; +import com.azure.data.tables.TableAzureNamedKeyCredentialPolicy; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.time.OffsetDateTime; +import java.util.Base64; + +/** + * This class provides helper methods used when generating SAS. + */ +public class TableSasUtils { + private static final String STRING_TO_SIGN_LOG_INFO_MESSAGE = "The string to sign computed by the SDK is: {}{}"; + private static final String STRING_TO_SIGN_LOG_WARNING_MESSAGE = "Please remember to disable '{}' before going " + + "to production as this string can potentially contain PII."; + + /** + * Shared helper method to append a SAS query parameter. + * + * @param sb The {@link StringBuilder} to append to. + * @param param The {@link String} parameter to append. + * @param value The value of the parameter to append. + */ + public static void tryAppendQueryParameter(StringBuilder sb, String param, Object value) { + if (value != null) { + if (sb.length() != 0) { + sb.append('&'); + } + + sb.append(TableUtils.urlEncode(param)).append('=').append(TableUtils.urlEncode(value.toString())); + } + } + + /** + * Formats date time SAS query parameters. + * + * @param dateTime The SAS date time. + * @return A String representing the SAS date time. + */ + public static String formatQueryParameterDate(OffsetDateTime dateTime) { + if (dateTime == null) { + return null; + } else { + return StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(dateTime); + } + } + + /** + * Logs the string to sign if a valid context is provided. + * + * @param logger A {@link ClientLogger} to log the signed string to. + * @param stringToSign The string to sign to log. + * @param context Additional context to determine if the string to sign should be logged. + */ + public static void logStringToSign(ClientLogger logger, String stringToSign, Context context) { + if (context != null && Boolean.TRUE.equals(context.getData(StorageConstants.STORAGE_LOG_STRING_TO_SIGN).orElse(false))) { + logger.info(STRING_TO_SIGN_LOG_INFO_MESSAGE, stringToSign, System.lineSeparator()); + logger.warning(STRING_TO_SIGN_LOG_WARNING_MESSAGE, StorageConstants.STORAGE_LOG_STRING_TO_SIGN); + } + } + + /** + * Extracts the {@link AzureNamedKeyCredential} from a {@link HttpPipeline} + * + * @param pipeline An {@link HttpPipeline} to extract an {@link AzureNamedKeyCredential} from. + * + * @return The extracted {@link AzureNamedKeyCredential}. + */ + public static AzureNamedKeyCredential extractNamedKeyCredential(HttpPipeline pipeline) { + for (int i = 0; i < pipeline.getPolicyCount(); i++) { + if (pipeline.getPolicy(i) instanceof TableAzureNamedKeyCredentialPolicy) { + TableAzureNamedKeyCredentialPolicy policy = (TableAzureNamedKeyCredentialPolicy) pipeline.getPolicy(i); + + return policy.getCredential(); + } + } + + return null; + } + + /** + * Computes a signature for the specified string using the HMAC-SHA256 algorithm. + * + * @param base64Key Base64 encoded key used to sign the string + * @param stringToSign UTF-8 encoded string to sign + * + * @return the HMAC-SHA256 encoded signature + * + * @throws RuntimeException If the HMAC-SHA256 algorithm isn't support, if the key isn't a valid Base64 encoded + * string, or the UTF-8 charset isn't supported. + */ + public static String computeHMac256(final String base64Key, final String stringToSign) { + try { + byte[] key = Base64.getDecoder().decode(base64Key); + Mac hmacSHA256 = Mac.getInstance("HmacSHA256"); + hmacSHA256.init(new SecretKeySpec(key, "HmacSHA256")); + byte[] utf8Bytes = stringToSign.getBytes(StandardCharsets.UTF_8); + + return Base64.getEncoder().encodeToString(hmacSHA256.doFinal(utf8Bytes)); + } catch (NoSuchAlgorithmException | InvalidKeyException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java new file mode 100644 index 0000000000000..7126db6b7b58b --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.implementation; + +import com.azure.core.credential.AzureNamedKeyCredential; +import com.azure.core.util.Context; +import com.azure.core.util.logging.ClientLogger; +import com.azure.data.tables.TableServiceVersion; +import com.azure.data.tables.sas.TableSasIpRange; +import com.azure.data.tables.sas.TableSasProtocol; +import com.azure.data.tables.sas.TableServiceSasPermission; +import com.azure.data.tables.sas.TableServiceSasSignatureValues; + +import java.time.OffsetDateTime; +import java.util.Objects; + +import static com.azure.data.tables.implementation.TableSasUtils.computeHMac256; +import static com.azure.data.tables.implementation.TableSasUtils.formatQueryParameterDate; +import static com.azure.data.tables.implementation.TableSasUtils.logStringToSign; +import static com.azure.data.tables.implementation.TableSasUtils.tryAppendQueryParameter; + +/** + * A class containing utility methods for generating SAS tokens for the Azure Data Tables service. + */ +public class TableServiceSasGenerator { + private final ClientLogger logger = new ClientLogger(TableServiceSasGenerator.class); + + private String version; + private TableSasProtocol protocol; + private OffsetDateTime startTime; + private OffsetDateTime expiryTime; + private String permissions; + private TableSasIpRange sasIpRange; + private String tableName; + private String identifier; + private String startPartitionKey; + private String startRowKey; + private String endPartitionKey; + private String endRowKey; + + /** + * Creates a new {@link TableServiceSasGenerator} with the specified parameters + * + * @param sasValues The {@link TableServiceSasSignatureValues} to generate the SAS token with. + * @param tableName The table name. + */ + public TableServiceSasGenerator(TableServiceSasSignatureValues sasValues, String tableName) { + Objects.requireNonNull(sasValues, "'sasValues' cannot be null."); + + this.version = sasValues.getVersion(); + this.protocol = sasValues.getProtocol(); + this.startTime = sasValues.getStartTime(); + this.expiryTime = sasValues.getExpiryTime(); + this.permissions = sasValues.getPermissions(); + this.sasIpRange = sasValues.getSasIpRange(); + this.tableName = tableName; + this.identifier = sasValues.getIdentifier(); + this.startPartitionKey = sasValues.getStartPartitionKey(); + this.startRowKey = sasValues.getStartRowKey(); + this.endPartitionKey = sasValues.getEndPartitionKey(); + this.endRowKey = sasValues.getEndRowKey(); + } + + /** + * Generates a SAS signed with an {@link AzureNamedKeyCredential}. + * + * @param azureNamedKeyCredential {@link AzureNamedKeyCredential}. + * @param context Additional context that is passed through the code when generating a SAS. + * + * @return A {@link String} representing the SAS. + */ + public String generateSas(AzureNamedKeyCredential azureNamedKeyCredential, Context context) { + Objects.requireNonNull(azureNamedKeyCredential, "'azureNamedKeyCredential' cannot be null."); + + ensureState(); + + // Signature is generated on the un-url-encoded values. + String canonicalName = getCanonicalName(azureNamedKeyCredential.getAzureNamedKey().getName()); + String stringToSign = stringToSign(canonicalName); + logStringToSign(logger, stringToSign, context); + String signature = computeHMac256(azureNamedKeyCredential.getAzureNamedKey().getKey(), stringToSign); + + return encode(signature); + } + + private String encode(String signature) { + /* + * We should be url-encoding each key and each value, but because we know all the keys and values will encode to + * themselves, we cheat except for the signature value. + */ + StringBuilder sb = new StringBuilder(); + + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SERVICE_VERSION, this.version); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_PROTOCOL, this.protocol); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_START_TIME, + formatQueryParameterDate(this.startTime)); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_EXPIRY_TIME, + formatQueryParameterDate(this.expiryTime)); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNED_IDENTIFIER, this.identifier); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNATURE, signature); + + return sb.toString(); + } + + /** + * Ensures that the builder's properties are in a consistent state. + * + * 1. If there is no version, use latest. + * 2. If there is no identifier set, ensure expiryTime and permissions are set. + * 4. Re-parse permissions depending on what the resource is. If it is an unrecognised resource, do nothing. + */ + private void ensureState() { + if (version == null) { + version = TableServiceVersion.getLatest().getVersion(); + } + + if (identifier == null) { + if (expiryTime == null || permissions == null) { + throw logger.logExceptionAsError(new IllegalStateException("If identifier is not set, expiry time " + + "and permissions must be set")); + } + } + + if (permissions != null) { + if (tableName != null) { + permissions = TableServiceSasPermission.parse(permissions).toString(); + } else { + // We won't re-parse the permissions if we don't know the type. + logger.info("Not re-parsing permissions. Resource type is not table."); + } + } + + if ((startPartitionKey != null && startRowKey == null) || (startPartitionKey == null && startRowKey != null)) { + throw logger.logExceptionAsError(new IllegalStateException("'startPartitionKey' and 'startRowKey' must " + + "either be both provided or both null. One cannot be provided without the other.")); + } + + if ((endPartitionKey != null && endRowKey == null) || (endPartitionKey == null && endRowKey != null)) { + throw logger.logExceptionAsError(new IllegalStateException("'endPartitionKey' and 'endRowKey' must either " + + "be both provided or both null. One cannot be provided without the other.")); + } + } + + /** + * Computes the canonical name for a table resource for SAS signing. + * + * @param account Account of the storage account. + * + * @return Canonical name as a string. + */ + private String getCanonicalName(String account) { + // Table: "/table/account/tablename" + return String.join("", new String[]{"/table/", account, "/", tableName}); + } + + private String stringToSign(String canonicalName) { + return String.join("\n", + this.permissions == null ? "" : this.permissions, + this.startTime == null ? "" : StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), + this.expiryTime == null ? "" : StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), + canonicalName, + this.identifier == null ? "" : this.identifier, + this.sasIpRange == null ? "" : this.sasIpRange.toString(), + this.protocol == null ? "" : protocol.toString(), + this.version == null ? "" : this.version, + this.startPartitionKey == null ? "" : this.startPartitionKey, + this.startRowKey == null ? "" : this.startRowKey, + this.endPartitionKey == null ? "" : this.endPartitionKey, + this.endRowKey == null ? "" : this.endRowKey + ); + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java index 4b062111ed5c7..682a8e64c73af 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java @@ -8,6 +8,7 @@ import com.azure.core.http.rest.Response; import com.azure.core.http.rest.SimpleResponse; import com.azure.core.util.CoreUtils; +import com.azure.core.util.UrlBuilder; import com.azure.core.util.logging.ClientLogger; import com.azure.data.tables.implementation.models.TableServiceErrorException; import com.azure.data.tables.implementation.models.TableServiceErrorOdataError; @@ -17,15 +18,15 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; +import java.net.URLEncoder; import java.time.Duration; -import java.util.Base64; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Comparator; import java.util.Locale; import java.util.Map; import java.util.TreeMap; @@ -37,6 +38,24 @@ */ public final class TableUtils { private static final String UTF8_CHARSET = "UTF-8"; + private static final String INVALID_DATE_STRING = "Invalid Date String: %s."; + /** + * Stores a reference to the date/time pattern with the greatest precision Java.util.Date is capable of expressing. + */ + private static final String MAX_PRECISION_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + /** + * Stores a reference to the ISO8601 date/time pattern. + */ + private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + /** + * Stores a reference to the ISO8601 date/time pattern. + */ + private static final String ISO8601_PATTERN_NO_SECONDS = "yyyy-MM-dd'T'HH:mm'Z'"; + /** + * The length of a datestring that matches the MAX_PRECISION_PATTERN. + */ + private static final int MAX_PRECISION_DATESTRING_LENGTH = MAX_PRECISION_PATTERN.replaceAll("'", "") + .length(); private TableUtils() { throw new UnsupportedOperationException("Cannot instantiate TablesUtils"); @@ -177,30 +196,6 @@ public static Mono> swallowExce return monoError(logger, httpResponseException); } - /** - * Computes a signature for the specified string using the HMAC-SHA256 algorithm. - * - * @param base64Key Base64 encoded key used to sign the string - * @param stringToSign UTF-8 encoded string to sign - * - * @return the HMAC-SHA256 encoded signature - * - * @throws RuntimeException If the HMAC-SHA256 algorithm isn't support, if the key isn't a valid Base64 encoded - * string, or the UTF-8 charset isn't supported. - */ - public static String computeHMac256(final String base64Key, final String stringToSign) { - try { - byte[] key = Base64.getDecoder().decode(base64Key); - Mac hmacSHA256 = Mac.getInstance("HmacSHA256"); - hmacSHA256.init(new SecretKeySpec(key, "HmacSHA256")); - byte[] utf8Bytes = stringToSign.getBytes(StandardCharsets.UTF_8); - - return Base64.getEncoder().encodeToString(hmacSHA256.doFinal(utf8Bytes)); - } catch (NoSuchAlgorithmException | InvalidKeyException ex) { - throw new RuntimeException(ex); - } - } - /** * Parses the query string into a key-value pair map that maintains key, query parameter key, order. The value is * stored as a parsed array (ex. key=[val1, val2, val3] instead of key=val1,val2,val3). @@ -294,4 +289,159 @@ private static String decode(final String stringToDecode) { throw new RuntimeException(ex); } } + + /** + * Performs a safe encoding of the specified string, taking care to insert %20 for each space character instead of + * inserting the {@code +} character. + * + * @param stringToEncode String value to encode + * @return the encoded string value + * @throws RuntimeException If the UTF-8 charset isn't supported + */ + public static String urlEncode(final String stringToEncode) { + if (stringToEncode == null) { + return null; + } + + if (stringToEncode.length() == 0) { + return ""; + } + + if (stringToEncode.contains(" ")) { + StringBuilder outBuilder = new StringBuilder(); + + int startDex = 0; + for (int m = 0; m < stringToEncode.length(); m++) { + if (stringToEncode.charAt(m) == ' ') { + if (m > startDex) { + outBuilder.append(encode(stringToEncode.substring(startDex, m))); + } + + outBuilder.append("%20"); + startDex = m + 1; + } + } + + if (startDex != stringToEncode.length()) { + outBuilder.append(encode(stringToEncode.substring(startDex))); + } + + return outBuilder.toString(); + } else { + return encode(stringToEncode); + } + } + + /* + * Helper method to reduce duplicate calls of URLEncoder.encode + */ + private static String encode(final String stringToEncode) { + try { + return URLEncoder.encode(stringToEncode, UTF8_CHARSET); + } catch (UnsupportedEncodingException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Performs a safe encoding of a url string, only encoding the path. + * + * @param url The url to encode. + * @return The encoded url. + */ + public static String encodeUrlPath(String url) { + /* Deconstruct the URL and reconstruct it making sure the path is encoded. */ + UrlBuilder builder = UrlBuilder.parse(url); + String path = builder.getPath(); + if (path.startsWith("/")) { + path = path.substring(1); + } + path = urlEncode(urlDecode(path)); + builder.setPath(path); + return builder.toString(); + } + + /** + * Given a String representing a date in a form of the ISO8601 pattern, generates a Date representing it with up to + * millisecond precision. + * + * @param dateString the {@code String} to be interpreted as a Date + * @return the corresponding Date object + * @throws IllegalArgumentException If {@code dateString} doesn't match an ISO8601 pattern + */ + public static OffsetDateTime parseDate(String dateString) { + String pattern = MAX_PRECISION_PATTERN; + switch (dateString.length()) { + case 28: // "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"-> [2012-01-04T23:21:59.1234567Z] length = 28 + case 27: // "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"-> [2012-01-04T23:21:59.123456Z] length = 27 + case 26: // "yyyy-MM-dd'T'HH:mm:ss.SSSSS'Z'"-> [2012-01-04T23:21:59.12345Z] length = 26 + case 25: // "yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'"-> [2012-01-04T23:21:59.1234Z] length = 25 + case 24: // "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"-> [2012-01-04T23:21:59.123Z] length = 24 + dateString = dateString.substring(0, MAX_PRECISION_DATESTRING_LENGTH); + break; + case 23: // "yyyy-MM-dd'T'HH:mm:ss.SS'Z'"-> [2012-01-04T23:21:59.12Z] length = 23 + // SS is assumed to be milliseconds, so a trailing 0 is necessary + dateString = dateString.replace("Z", "0"); + break; + case 22: // "yyyy-MM-dd'T'HH:mm:ss.S'Z'"-> [2012-01-04T23:21:59.1Z] length = 22 + // S is assumed to be milliseconds, so trailing 0's are necessary + dateString = dateString.replace("Z", "00"); + break; + case 20: // "yyyy-MM-dd'T'HH:mm:ss'Z'"-> [2012-01-04T23:21:59Z] length = 20 + pattern = ISO8601_PATTERN; + break; + case 17: // "yyyy-MM-dd'T'HH:mm'Z'"-> [2012-01-04T23:21Z] length = 17 + pattern = ISO8601_PATTERN_NO_SECONDS; + break; + default: + throw new IllegalArgumentException(String.format(Locale.ROOT, INVALID_DATE_STRING, dateString)); + } + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, Locale.ROOT); + return LocalDateTime.parse(dateString, formatter).atZone(ZoneOffset.UTC).toOffsetDateTime(); + } + + /** + * Parses a query string into a one to many TreeMap. + * + * @param queryParams The string of query params to parse. + * @return A {@code HashMap} of the key values. + */ + public static Map parseQueryString(String queryParams) { + final TreeMap retVals = new TreeMap<>(Comparator.naturalOrder()); + + if (CoreUtils.isNullOrEmpty(queryParams)) { + return retVals; + } + + // split name value pairs by splitting on the '&' character + final String[] valuePairs = queryParams.split("&"); + + // for each field value pair parse into appropriate map entries + for (String valuePair : valuePairs) { + // Getting key and value for a single query parameter + final int equalDex = valuePair.indexOf("="); + String key = urlDecode(valuePair.substring(0, equalDex)).toLowerCase(Locale.ROOT); + String value = urlDecode(valuePair.substring(equalDex + 1)); + + // add to map + String[] keyValues = retVals.get(key); + + // check if map already contains key + if (keyValues == null) { + // map does not contain this key + keyValues = new String[]{value}; + } else { + // map contains this key already so append + final String[] newValues = new String[keyValues.length + 1]; + System.arraycopy(keyValues, 0, newValues, 0, keyValues.length); + + newValues[newValues.length - 1] = value; + keyValues = newValues; + } + retVals.put(key, keyValues); + } + + return retVals; + } } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java new file mode 100644 index 0000000000000..a5f2cb6b227f6 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java @@ -0,0 +1,409 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.sas; + +import com.azure.data.tables.implementation.StorageConstants; + +import java.util.Locale; + +/** + * This is a helper class to construct a string representing the permissions granted by an Account SAS. Setting a value + * to true means that any SAS which uses these permissions will grant permissions for that operation. Once all the + * values are set, this should be serialized with {@code toString()} and set as the permissions field on an + * {@link TableAccountSasSignatureValues} object. + * + *

+ * It is possible to construct the permissions string without this class, but the order of the permissions is particular + * and this class guarantees correctness. + *

+ * + * @see TableAccountSasSignatureValues + * @see Create account SAS + */ +public final class TableAccountSasPermission { + private boolean readPermission; + private boolean addPermission; + private boolean createPermission; + private boolean writePermission; + private boolean deletePermission; + private boolean deleteVersionPermission; + private boolean listPermission; + private boolean updatePermission; + private boolean processMessagesPermission; + private boolean tagsPermission; + private boolean filterTagsPermission; + + /** + * Initializes an {@link TableAccountSasPermission} object with all fields set to {@code false}. + */ + public TableAccountSasPermission() { + } + + /** + * Creates an {@link TableAccountSasPermission} from the specified permissions string. This method will throw an + * {@link IllegalArgumentException} if it encounters a character that does not correspond to a valid permission. + * + * @param permissionsString A {@code String} which represents the {@link TableAccountSasPermission account permissions}. + * + * @return An {@link TableAccountSasPermission} object generated from the given {@link String}. + * + * @throws IllegalArgumentException If {@code permString} contains a character other than r, w, d, x, l, a, c, u, p, + * t or f. + */ + public static TableAccountSasPermission parse(String permissionsString) { + TableAccountSasPermission permissions = new TableAccountSasPermission(); + + for (int i = 0; i < permissionsString.length(); i++) { + char c = permissionsString.charAt(i); + switch (c) { + case 'r': + permissions.readPermission = true; + break; + case 'w': + permissions.writePermission = true; + break; + case 'd': + permissions.deletePermission = true; + break; + case 'x': + permissions.deleteVersionPermission = true; + break; + case 'l': + permissions.listPermission = true; + break; + case 'a': + permissions.addPermission = true; + break; + case 'c': + permissions.createPermission = true; + break; + case 'u': + permissions.updatePermission = true; + break; + case 'p': + permissions.processMessagesPermission = true; + break; + case 't': + permissions.tagsPermission = true; + break; + case 'f': + permissions.filterTagsPermission = true; + break; + default: + throw new IllegalArgumentException( + String.format(Locale.ROOT, StorageConstants.ENUM_COULD_NOT_BE_PARSED_INVALID_VALUE, + "Permissions", permissionsString, c)); + } + } + + return permissions; + } + + /** + * Gets the read permission status. Valid for all signed resources types (Service, Container, and Object). Permits + * read permissions to the specified resource type. + * + * @return The read permission status. + */ + public boolean hasReadPermission() { + return readPermission; + } + + /** + * Sets the read permission status. Valid for all signed resources types (Service, Container, and Object). Permits + * read permissions to the specified resource type. + * + * @param hasReadPermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setReadPermission(boolean hasReadPermission) { + this.readPermission = hasReadPermission; + + return this; + } + + /** + * Gets the add permission status. Valid for the following Object resource types only: queue messages, table + * entities, and append blobs. + * + * @return The add permission status. + */ + public boolean hasAddPermission() { + return addPermission; + } + + /** + * Sets the add permission status. Valid for the following Object resource types only: queue messages, table + * entities, and append blobs. + * + * @param hasAddPermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setAddPermission(boolean hasAddPermission) { + this.addPermission = hasAddPermission; + + return this; + } + + /** + * Gets the create permission status. Valid for the following Object resource types only: blobs and files. Users can + * create new blobs or files, but may not overwrite existing blobs or files. + * + * @return The create permission status. + */ + public boolean hasCreatePermission() { + return createPermission; + } + + /** + * Sets the create permission status. Valid for the following Object resource types only: blobs and files. Users can + * create new blobs or files, but may not overwrite existing blobs or files. + * + * @param hasCreatePermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setCreatePermission(boolean hasCreatePermission) { + this.createPermission = hasCreatePermission; + + return this; + } + + /** + * Gets the write permission status. Valid for all signed resources types (Service, Container, and Object). Permits + * write permissions to the specified resource type. + * + * @return The write permission status. + */ + public boolean hasWritePermission() { + return writePermission; + } + + /** + * Sets the write permission status. Valid for all signed resources types (Service, Container, and Object). Permits + * write permissions to the specified resource type. + * + * @param hasWritePermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setWritePermission(boolean hasWritePermission) { + this.writePermission = hasWritePermission; + + return this; + } + + /** + * Gets the delete permission status. Valid for Container and Object resource types, except for queue messages. + * + * @return The delete permission status. + */ + public boolean hasDeletePermission() { + return deletePermission; + } + + /** + * Sets the delete permission status. Valid for Container and Object resource types, except for queue messages. + * + * @param hasDeletePermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setDeletePermission(boolean hasDeletePermission) { + this.deletePermission = hasDeletePermission; + + return this; + } + + /** + * Gets the delete version permission status. Used to delete a blob version + * + * @return The delete version permission status. + */ + public boolean hasDeleteVersionPermission() { + return deleteVersionPermission; + } + + /** + * Sets the delete version permission status. Used to delete a blob version + * + * @param hasDeleteVersionPermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setDeleteVersionPermission(boolean hasDeleteVersionPermission) { + this.deleteVersionPermission = hasDeleteVersionPermission; + + return this; + } + + /** + * Gets the list permission status. Valid for Service and Container resource types only. + * + * @return The list permission status. + */ + public boolean hasListPermission() { + return listPermission; + } + + /** + * Sets the list permission status. Valid for Service and Container resource types only. + * + * @param hasListPermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setListPermission(boolean hasListPermission) { + this.listPermission = hasListPermission; + + return this; + } + + /** + * Gets the update permission status. Valid for the following Object resource types only: queue messages and table + * entities. + * + * @return The update permission status. + */ + public boolean hasUpdatePermission() { + return updatePermission; + } + + /** + * Sets the update permission status. Valid for the following Object resource types only: queue messages and table + * entities. + * + * @param hasUpdatePermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setUpdatePermission(boolean hasUpdatePermission) { + this.updatePermission = hasUpdatePermission; + + return this; + } + + /** + * Gets the process messages permission. Valid for the following Object resource type only: queue messages. + * + * @return The process messages permission status. + */ + public boolean hasProcessMessages() { + return processMessagesPermission; + } + + /** + * Sets the process messages permission. Valid for the following Object resource type only: queue messages. + * + * @param hasProcessMessagesPermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setProcessMessages(boolean hasProcessMessagesPermission) { + this.processMessagesPermission = hasProcessMessagesPermission; + + return this; + } + + /** + * @return The tags permission status. Used to read or write the tags on a blob. + */ + public boolean hasTagsPermission() { + return tagsPermission; + } + + /** + * Sets the tags permission status. + * + * @param tagsPermission The permission status to set. Used to read or write the tags on a blob. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setTagsPermission(boolean tagsPermission) { + this.tagsPermission = tagsPermission; + + return this; + } + + + /** + * @return The filter tags permission status. Used to filter blobs by their tags. + */ + public boolean hasFilterTagsPermission() { + return filterTagsPermission; + } + + /** + * Sets the filter tags permission status. Used to filter blobs by their tags. + * + * @param filterTagsPermission The permission status to set. + * + * @return The updated {@link TableAccountSasPermission} object. + */ + public TableAccountSasPermission setFilterTagsPermission(boolean filterTagsPermission) { + this.filterTagsPermission = filterTagsPermission; + + return this; + } + + /** + * Converts the given permissions to a {@link String}. Using this method will guarantee the permissions are in an + * order accepted by the service. + * + * @return A {@link String} which represents the {@link TableAccountSasPermission}. + */ + @Override + public String toString() { + // The order of the characters should be as specified here to ensure correctness: + // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas + final StringBuilder builder = new StringBuilder(); + + if (this.readPermission) { + builder.append('r'); + } + + if (this.writePermission) { + builder.append('w'); + } + + if (this.deletePermission) { + builder.append('d'); + } + + if (this.deleteVersionPermission) { + builder.append('x'); + } + + if (this.listPermission) { + builder.append('l'); + } + + if (this.addPermission) { + builder.append('a'); + } + + if (this.createPermission) { + builder.append('c'); + } + + if (this.updatePermission) { + builder.append('u'); + } + + if (this.processMessagesPermission) { + builder.append('p'); + } + + if (this.tagsPermission) { + builder.append('t'); + } + + if (this.filterTagsPermission) { + builder.append('f'); + } + + return builder.toString(); + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java new file mode 100644 index 0000000000000..e2a6c9059107c --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.sas; + +import com.azure.data.tables.implementation.StorageConstants; + +import java.util.Locale; + +/** + * This is a helper class to construct a string representing the resources accessible by an Account SAS. Setting a value + * to true means that any SAS which uses these permissions will grant access to that resource type. Once all the values + * are set, this should be serialized with {@code toString()} and set as the resources field on an + * {@link TableAccountSasSignatureValues} object. It is possible to construct the resources string without this class, + * but the order of the resources is particular and this class guarantees correctness. + */ +public final class TableAccountSasResourceType { + private boolean service; + private boolean container; + private boolean object; + + /** + * Initializes an {@link TableAccountSasResourceType} object with all fields set to {@code false}. + */ + public TableAccountSasResourceType() { + } + + /** + * Creates an {@link TableAccountSasResourceType} from the specified resource types string. This method will throw an + * {@link IllegalArgumentException} if it encounters a character that does not correspond to a valid resource type. + * + * @param resourceTypesString A {@code String} which represents the + * {@link TableAccountSasResourceType account resource types}. + * + * @return A {@link TableAccountSasResourceType} generated from the given {@link String}. + * + * @throws IllegalArgumentException If {@code resourceTypesString} contains a character other than s, c, or o. + */ + public static TableAccountSasResourceType parse(String resourceTypesString) { + TableAccountSasResourceType resourceType = new TableAccountSasResourceType(); + + for (int i = 0; i < resourceTypesString.length(); i++) { + char c = resourceTypesString.charAt(i); + + switch (c) { + case 's': + resourceType.service = true; + break; + case 'c': + resourceType.container = true; + break; + case 'o': + resourceType.object = true; + break; + default: + throw new IllegalArgumentException( + String.format(Locale.ROOT, StorageConstants.ENUM_COULD_NOT_BE_PARSED_INVALID_VALUE, + "Resource Types", resourceTypesString, c)); + } + } + + return resourceType; + } + + /** + * Get the access status for service level APIs. + * + * @return The access status for service level APIs. + */ + public boolean isService() { + return service; + } + + /** + * Sets the access status for service level APIs. + * + * @param service The access status to set. + * + * @return The updated {@link TableAccountSasResourceType} object. + */ + public TableAccountSasResourceType setService(boolean service) { + this.service = service; + + return this; + } + + /** + * Gets the access status for container level APIs, this grants access to Blob Containers, Tables, Queues, and + * File Shares. + * + * @return The access status for container level APIs, this grants access to Blob Containers, Tables, Queues, and + * File Shares. + */ + public boolean isContainer() { + return container; + } + + /** + * Sets the access status for container level APIs, this grants access to Blob Containers, Tables, Queues, and File + * Shares. + * + * @param container The access status to set. + * + * @return The updated {@link TableAccountSasResourceType} object. + */ + public TableAccountSasResourceType setContainer(boolean container) { + this.container = container; + + return this; + } + + /** + * Get the access status for object level APIs, this grants access to Blobs, Table Entities, Queue Messages, Files. + * + * @return The access status for object level APIs, this grants access to Blobs, Table Entities, Queue Messages, + * Files. + */ + public boolean isObject() { + return object; + } + + /** + * Sets the access status for object level APIs, this grants access to Blobs, Table Entities, Queue Messages, + * Files. + * + * @param object The access status to set. + * + * @return The updated {@link TableAccountSasResourceType} object. + */ + public TableAccountSasResourceType setObject(boolean object) { + this.object = object; + + return this; + } + + /** + * Converts the given resource types to a {@link String}. Using this method will guarantee the resource types are in + * an order accepted by the service. + * + * @return A {@code String} which represents the {@link TableAccountSasResourceType account resource types}. + */ + @Override + public String toString() { + // The order of the characters should be as specified here to ensure correctness: + // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas + StringBuilder builder = new StringBuilder(); + + if (this.service) { + builder.append('s'); + } + + if (this.container) { + builder.append('c'); + } + + if (this.object) { + builder.append('o'); + } + + return builder.toString(); + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java new file mode 100644 index 0000000000000..4cc45751b6ec5 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.sas; + +import com.azure.data.tables.implementation.StorageConstants; + +import java.util.Locale; + +/** + * This is a helper class to construct a string representing the services accessible by an Account SAS. Setting a value + * to true means that any SAS which uses these permissions will grant access to that service. Once all the values are + * set, this should be serialized with {@code toString()} and set as the services field on an + * {@link TableAccountSasSignatureValues} object. It is possible to construct the services string without this class, but + * the order of the services is particular and this class guarantees correctness. + */ +public final class TableAccountSasService { + private boolean blob; + private boolean file; + private boolean queue; + private boolean table; + + /** + * Initializes an {@code AccountSasService} object with all fields set to {@code false}. + */ + public TableAccountSasService() { + } + + /** + * Creates an {@link TableAccountSasService} from the specified services string. This method will throw an + * {@link IllegalArgumentException} if it encounters a character that does not correspond to a valid service. + * + * @param servicesString A {@link String} which represents the {@link TableAccountSasService account services}. + * + * @return A {@link TableAccountSasService} generated from the given {@link String}. + * + * @throws IllegalArgumentException If {@code servicesString} contains a character other than b, f, q, or t. + */ + public static TableAccountSasService parse(String servicesString) { + TableAccountSasService services = new TableAccountSasService(); + + for (int i = 0; i < servicesString.length(); i++) { + char c = servicesString.charAt(i); + switch (c) { + case 'b': + services.blob = true; + break; + case 'f': + services.file = true; + break; + case 'q': + services.queue = true; + break; + case 't': + services.table = true; + break; + default: + throw new IllegalArgumentException( + String.format(Locale.ROOT, StorageConstants.ENUM_COULD_NOT_BE_PARSED_INVALID_VALUE, "Services", + servicesString, c)); + } + } + + return services; + } + + /** + * @return The access status for blob resources. + */ + public boolean hasBlobAccess() { + return blob; + } + + /** + * Sets the access status for blob resources. + * + * @param blob The access status to set. + * + * @return The updated {@link TableAccountSasService} object. + */ + public TableAccountSasService setBlobAccess(boolean blob) { + this.blob = blob; + + return this; + } + + /** + * @return The access status for file resources. + */ + public boolean hasFileAccess() { + return file; + } + + /** + * Sets the access status for file resources. + * + * @param file The access status to set. + * + * @return The updated {@link TableAccountSasService} object. + */ + public TableAccountSasService setFileAccess(boolean file) { + this.file = file; + + return this; + } + + /** + * @return The access status for queue resources. + */ + public boolean hasQueueAccess() { + return queue; + } + + /** + * Sets the access status for queue resources. + * + * @param queue The access status to set. + * + * @return The updated {@link TableAccountSasService} object. + */ + public TableAccountSasService setQueueAccess(boolean queue) { + this.queue = queue; + + return this; + } + + /** + * @return The access status for table resources. + */ + public boolean hasTableAccess() { + return table; + } + + /** + * Sets the access status for table resources. + * + * @param table The access status to set. + * + * @return The updated {@link TableAccountSasService} object. + */ + public TableAccountSasService setTableAccess(boolean table) { + this.table = table; + + return this; + } + + /** + * Converts the given services to a {@link String}. Using this method will guarantee the services are in an order + * accepted by the service. + * + * @return A {@link String} which represents the {@link TableAccountSasService account services}. + */ + @Override + public String toString() { + // The order of the characters should be as specified here to ensure correctness: + // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas + StringBuilder value = new StringBuilder(); + + if (this.blob) { + value.append('b'); + } + if (this.queue) { + value.append('q'); + } + if (this.table) { + value.append('t'); + } + if (this.file) { + value.append('f'); + } + + return value.toString(); + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java new file mode 100644 index 0000000000000..fe941b4be0c83 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java @@ -0,0 +1,198 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.sas; + +import com.azure.core.credential.AzureNamedKeyCredential; +import com.azure.data.tables.implementation.StorageConstants; + +import java.time.OffsetDateTime; +import java.util.Objects; + +/** + * Used to initialize parameters for a Shared Access Signature (SAS) for an Azure Storage account. Once all the values + * here are set, use the {@code generateAccountSas()} method on the desired service client to obtain a + * representation of the SAS which can then be applied to a new client using the {@code sasToken(String)} method on + * the desired client builder. + * + * @see Storage SAS overview + * @see Create an account SAS + */ +public final class TableAccountSasSignatureValues { + private String version; + private TableSasProtocol protocol; + private OffsetDateTime startTime; + private OffsetDateTime expiryTime; + private String permissions; + private TableSasIpRange sasIpRange; + private String services; + private String resourceTypes; + + /** + * Initializes a new {@link TableAccountSasSignatureValues} object. + * + * @param expiryTime The time after which the SAS will no longer work. + * @param permissions {@link TableAccountSasPermission account permissions} allowed by the SAS. + * @param services {@link TableAccountSasService account services} targeted by the SAS. + * @param resourceTypes {@link TableAccountSasResourceType account resource types} targeted by the SAS. + */ + public TableAccountSasSignatureValues(OffsetDateTime expiryTime, TableAccountSasPermission permissions, + TableAccountSasService services, TableAccountSasResourceType resourceTypes) { + + Objects.requireNonNull(expiryTime, "'expiryTime' cannot be null"); + Objects.requireNonNull(services, "'services' cannot be null"); + Objects.requireNonNull(permissions, "'permissions' cannot be null"); + Objects.requireNonNull(resourceTypes, "'resourceTypes' cannot be null"); + + this.expiryTime = expiryTime; + this.services = services.toString(); + this.resourceTypes = resourceTypes.toString(); + this.permissions = permissions.toString(); + } + + /** + * Get the service version that is targeted, if {@code null} or empty the latest service version targeted by the + * library will be used. + * + * @return The service version that is targeted. + */ + public String getVersion() { + return version; + } + + /** + * Sets the service version that is targeted. Leave this {@code null} or empty to target the version used by the + * library. + * + * @param version The target version to set. + * + * @return The updated {@link TableAccountSasSignatureValues} object. + */ + public TableAccountSasSignatureValues setVersion(String version) { + this.version = version; + + return this; + } + + /** + * Get the {@link TableSasProtocol} which determines the HTTP protocol that will be used. + * + * @return The {@link TableSasProtocol}. + */ + public TableSasProtocol getProtocol() { + return protocol; + } + + /** + * Sets the {@link TableSasProtocol} which determines the HTTP protocol that will be used. + * + * @param protocol The {@link TableSasProtocol} to set. + * + * @return The updated {@link TableAccountSasSignatureValues} object. + */ + public TableAccountSasSignatureValues setProtocol(TableSasProtocol protocol) { + this.protocol = protocol; + + return this; + } + + /** + * Get when the SAS will take effect. + * + * @return When the SAS will take effect. + */ + public OffsetDateTime getStartTime() { + return startTime; + } + + /** + * Sets when the SAS will take effect. + * + * @param startTime The start time to set. + * + * @return The updated {@link TableAccountSasSignatureValues} object. + */ + public TableAccountSasSignatureValues setStartTime(OffsetDateTime startTime) { + this.startTime = startTime; + + return this; + } + + /** + * Get The time after which the SAS will no longer work. + * + * @return The time after which the SAS will no longer work. + */ + public OffsetDateTime getExpiryTime() { + return expiryTime; + } + + /** + * Gets the operations the SAS user may perform. Please refer to {@link TableAccountSasPermission} to help determine + * which permissions are allowed. + * + * @return The operations the SAS user may perform. + */ + public String getPermissions() { + return permissions; + } + + /** + * Get the {@link TableSasIpRange} which determines the IP ranges that are allowed to use the SAS. + * + * @return The {@link TableSasIpRange}. + */ + public TableSasIpRange getSasIpRange() { + return sasIpRange; + } + + /** + * Sets the {@link TableSasIpRange} which determines the IP ranges that are allowed to use the SAS. + * + * @param sasIpRange The {@link TableSasIpRange allowed IP range} to set. + * + * @return The updated {@link TableAccountSasSignatureValues} object. + * + * @see Specifying + * IP Address or IP range + */ + public TableAccountSasSignatureValues setSasIpRange(TableSasIpRange sasIpRange) { + this.sasIpRange = sasIpRange; + + return this; + } + + /** + * Get the services accessible with this SAS. Please refer to {@link TableAccountSasService} to help determine which + * services are accessible. + * + * @return The services accessible with this SAS. + */ + public String getServices() { + return services; + } + + /** + * Get the resource types accessible with this SAS. Please refer to {@link TableAccountSasResourceType} to help determine + * the resource types that are accessible. + * + * @return The resource types accessible with this SAS. + */ + public String getResourceTypes() { + return resourceTypes; + } + + private String stringToSign(final AzureNamedKeyCredential azureNamedKeyCredential) { + return String.join("\n", + azureNamedKeyCredential.getAzureNamedKey().getName(), + TableAccountSasPermission.parse(this.permissions).toString(), // guarantees ordering + this.services, + resourceTypes, + this.startTime == null ? "" : StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), + StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), + this.sasIpRange == null ? "" : this.sasIpRange.toString(), + this.protocol == null ? "" : this.protocol.toString(), + this.version, + "" // Account SAS requires an additional newline character + ); + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java new file mode 100644 index 0000000000000..37e412809fdd5 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.sas; + +/** + * This type specifies a continuous range of IP addresses. It is used to limit permissions on SAS tokens. Null may be + * set if it is not desired to confine the sas permissions to an IP range. + */ +public final class TableSasIpRange { + private String ipMin; + private String ipMax; + + /** + * Constructs an SasIpRange object. + */ + public TableSasIpRange() { + } + + /** + * Creates a {@code SasIpRange} from the specified string. + * + * @param rangeStr The {@code String} representation of the {@code SasIpRange}. + * @return The {@code SasIpRange} generated from the {@code String}. + */ + public static TableSasIpRange parse(String rangeStr) { + String[] addrs = rangeStr.split("-"); + + TableSasIpRange range = new TableSasIpRange().setIpMin(addrs[0]); + if (addrs.length > 1) { + range.setIpMax(addrs[1]); + } + + return range; + } + + /** + * @return the minimum IP address of the range + */ + public String getIpMin() { + return ipMin; + } + + /** + * Sets the minimum IP address of the range. + * + * @param ipMin IP address to set as the minimum + * @return the updated SasIpRange object + */ + public TableSasIpRange setIpMin(String ipMin) { + this.ipMin = ipMin; + return this; + } + + /** + * @return the maximum IP address of the range + */ + public String getIpMax() { + return ipMax; + } + + /** + * Sets the maximum IP address of the range. + * + * @param ipMax IP address to set as the maximum + * @return the updated SasIpRange object + */ + public TableSasIpRange setIpMax(String ipMax) { + this.ipMax = ipMax; + return this; + } + + /** + * Output the single IP address or range of IP addresses for. + * + * @return The single IP address or range of IP addresses formatted as a {@code String}. + */ + @Override + public String toString() { + if (this.ipMin == null) { + return ""; + } else if (this.ipMax == null) { + return this.ipMin; + } else { + return this.ipMin + "-" + this.ipMax; + } + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/SasProtocol.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasProtocol.java similarity index 74% rename from sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/SasProtocol.java rename to sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasProtocol.java index 36463e7180b83..ca3b747a5d583 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/SasProtocol.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasProtocol.java @@ -1,13 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.data.tables.implementation; + +package com.azure.data.tables.sas; + +import com.azure.data.tables.implementation.StorageConstants; import java.util.Locale; /** * Specifies the set of possible permissions for Shared Access Signature protocol. */ -public enum SasProtocol { +public enum TableSasProtocol { /** * Permission to use SAS only through https granted. */ @@ -20,23 +23,23 @@ public enum SasProtocol { private final String protocols; - SasProtocol(String p) { + TableSasProtocol(String p) { this.protocols = p; } /** - * Parses a {@code String} into a {@link SasProtocol} value if possible. + * Parses a {@code String} into a {@link TableSasProtocol} value if possible. * * @param str The value to try to parse. * * @return A {@code SasProtocol} value that represents the string if possible. * @throws IllegalArgumentException If {@code str} doesn't equal "https" or "https,http". */ - public static SasProtocol parse(String str) { + public static TableSasProtocol parse(String str) { if (str.equals(StorageConstants.HTTPS)) { - return SasProtocol.HTTPS_ONLY; + return TableSasProtocol.HTTPS_ONLY; } else if (str.equals(StorageConstants.HTTPS_HTTP)) { - return SasProtocol.HTTPS_HTTP; + return TableSasProtocol.HTTPS_HTTP; } throw new IllegalArgumentException(String.format(Locale.ROOT, diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasPermission.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasPermission.java new file mode 100644 index 0000000000000..add6469b8cd0e --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasPermission.java @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.sas; + +import com.azure.data.tables.implementation.StorageConstants; + +import java.util.Locale; + +/** + * Constructs a string representing the permissions granted by an Azure Service SAS to a table. Setting a value to true + * means that any SAS which uses these permissions will grant permissions for that operation. Once all the values are + * set, this should be serialized with {@link #toString() toString} and set as the permissions field on + * {@link TableServiceSasSignatureValues#setPermissions(TableServiceSasPermission)} TableSasSignatureValues}. + * + *

+ * It is possible to construct the permissions string without this class, but the order of the permissions is + * particular and this class guarantees correctness. + *

+ * + * @see + * Permissions for a table + * @see TableServiceSasSignatureValues + */ +public class TableServiceSasPermission { + private boolean readPermission; + private boolean addPermission; + private boolean updatePermission; + private boolean processPermission; + + /** + * Initializes a {@link TableServiceSasPermission} object with all fields set to {@code false}. + */ + public TableServiceSasPermission() { + } + + /** + * Creates a {@link TableServiceSasPermission} from the specified permissions string. This method will throw an + * {@link IllegalArgumentException} if it encounters a character that does not correspond to a valid permission. + * + * @param permString A {@link String} which represents the {@link TableServiceSasPermission}. + * + * @return A {@link TableServiceSasPermission} generated from the given {@link String}. + * + * @throws IllegalArgumentException If {@code permString} contains a character other than r, a, u, or p. + */ + public static TableServiceSasPermission parse(String permString) { + TableServiceSasPermission permissions = new TableServiceSasPermission(); + + for (int i = 0; i < permString.length(); i++) { + char c = permString.charAt(i); + switch (c) { + case 'r': + permissions.readPermission = true; + + break; + case 'a': + permissions.addPermission = true; + + break; + case 'u': + permissions.updatePermission = true; + + break; + case 'p': + permissions.processPermission = true; + + break; + default: + throw new IllegalArgumentException( + String.format(Locale.ROOT, StorageConstants.ENUM_COULD_NOT_BE_PARSED_INVALID_VALUE, + "Permissions", permString, c)); + } + } + + return permissions; + } + + /** + * Gets the read permissions status. + * + * @return {@code true} if the SAS has permission to get entities and query entities. {@code false}, otherwise. + */ + public boolean hasReadPermission() { + return readPermission; + } + + /** + * Sets the read permission status. + * + * @param hasReadPermission {@code true} if the SAS has permission to get entities and query entities. + * {@code false}, otherwise + * + * @return The updated TableSasPermission object. + */ + public TableServiceSasPermission setReadPermission(boolean hasReadPermission) { + this.readPermission = hasReadPermission; + + return this; + } + + /** + * Gets the add permission status. + * + * @return {@code true} if the SAS has permission to add entities to the table. {@code false}, otherwise. + */ + public boolean hasAddPermission() { + return addPermission; + } + + /** + * Sets the add permission status. + * + * @param hasAddPermission {@code true} if the SAS has permission to add entities to the table. {@code false}, + * otherwise. + * + *

+ * Note: The {@code add} and {@code update} permissions are required for upsert operations. + *

+ * + * @return The updated {@link TableServiceSasPermission} object. + */ + public TableServiceSasPermission setAddPermission(boolean hasAddPermission) { + this.addPermission = hasAddPermission; + + return this; + } + + /** + * Gets the update permission status. + * + * @return {@code true} if the SAS has permission to update entities in the table. {@code false}, otherwise. + */ + public boolean hasUpdatePermission() { + return updatePermission; + } + + /** + * Sets the update permission status. + * + *

+ * Note: The {@code add} and {@code update} permissions are required for upsert operations. + *

+ * + * @param hasUpdatePermission {@code true} if the SAS has permission to update entities in the table. {@code false}, + * otherwise. + * + * @return The updated {@link TableServiceSasPermission} object. + */ + public TableServiceSasPermission setUpdatePermission(boolean hasUpdatePermission) { + this.updatePermission = hasUpdatePermission; + + return this; + } + + /** + * Gets the process permission status. + * + * @return {@code true} if the SAS has permission to delete entities from the table. {@code false}, otherwise. + */ + public boolean hasProcessPermission() { + return processPermission; + } + + /** + * Sets the process permission status. + * + * @param hasProcessPermission {@code true} if the SAS has permission to delete entities from the table. + * {@code false}, otherwise. + * + * @return The updated {@link TableServiceSasPermission} object. + */ + public TableServiceSasPermission setProcessPermission(boolean hasProcessPermission) { + this.processPermission = hasProcessPermission; + + return this; + } + + /** + * Converts the given permissions to a {@link String}. Using this method will guarantee the permissions are in an + * order accepted by the service. + * + * @return A {@link String} which represents the {@link TableServiceSasPermission}. + */ + @Override + public String toString() { + // The order of the characters should be as specified here to ensure correctness: + // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas + + final StringBuilder builder = new StringBuilder(); + + if (this.readPermission) { + builder.append('r'); + } + + if (this.addPermission) { + builder.append('a'); + } + + if (this.updatePermission) { + builder.append('u'); + } + + if (this.processPermission) { + builder.append('p'); + } + + return builder.toString(); + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasSignatureValues.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasSignatureValues.java new file mode 100644 index 0000000000000..f7790187be521 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasSignatureValues.java @@ -0,0 +1,312 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.sas; + +import java.time.OffsetDateTime; +import java.util.Objects; + +/** + * Used to initialize parameters for a Shared Access Signature (SAS) for the Azure Table Storage service. Once all the + * values here are set, use the {@code generateSas()} method on the desired Table client to obtain a representation + * of the SAS which can then be applied to a new client using the {@code sasToken(String)} method on the desired + * client builder. + * + * @see Storage SAS overview + * @see Constructing a Service SAS + */ +public class TableServiceSasSignatureValues { + private String version; + private TableSasProtocol protocol; + private OffsetDateTime startTime; + private OffsetDateTime expiryTime; + private String permissions; + private TableSasIpRange sasIpRange; + private String identifier; + private String startPartitionKey; + private String startRowKey; + private String endPartitionKey; + private String endRowKey; + + /** + * Creates an object with the specified expiry time and permissions. + * + * @param expiryTime The time after which the SAS will no longer work. + * @param permissions {@link TableServiceSasPermission table permissions} allowed by the SAS. + */ + public TableServiceSasSignatureValues(OffsetDateTime expiryTime, TableServiceSasPermission permissions) { + Objects.requireNonNull(expiryTime, "'expiryTime' cannot be null"); + Objects.requireNonNull(permissions, "'permissions' cannot be null"); + + this.expiryTime = expiryTime; + this.permissions = permissions.toString(); + } + + /** + * Creates an object with the specified identifier. + * + * @param identifier Name of the access policy. + */ + public TableServiceSasSignatureValues(String identifier) { + Objects.requireNonNull(identifier, "'identifier' cannot be null"); + + this.identifier = identifier; + } + + /** + * @return The version of the service this SAS will target. If not specified, it will default to the version + * targeted by the library. + */ + public String getVersion() { + return version; + } + + /** + * Sets the version of the service this SAS will target. If not specified, it will default to the version targeted + * by the library. + * + * @param version Version to target + * + * @return The updated {@link TableServiceSasSignatureValues} object. + */ + public TableServiceSasSignatureValues setVersion(String version) { + this.version = version; + + return this; + } + + /** + * @return The {@link TableSasProtocol} which determines the protocols allowed by the SAS. + */ + public TableSasProtocol getProtocol() { + return protocol; + } + + /** + * Sets the {@link TableSasProtocol} which determines the protocols allowed by the SAS. + * + * @param protocol Protocol for the SAS + * + * @return The updated {@link TableServiceSasSignatureValues} object. + */ + public TableServiceSasSignatureValues setProtocol(TableSasProtocol protocol) { + this.protocol = protocol; + + return this; + } + + /** + * @return When the SAS will take effect. + */ + public OffsetDateTime getStartTime() { + return startTime; + } + + /** + * Sets when the SAS will take effect. + * + * @param startTime When the SAS takes effect + * + * @return The updated {@link TableServiceSasSignatureValues} object. + */ + public TableServiceSasSignatureValues setStartTime(OffsetDateTime startTime) { + this.startTime = startTime; + + return this; + } + + /** + * @return The time after which the SAS will no longer work. + */ + public OffsetDateTime getExpiryTime() { + return expiryTime; + } + + /** + * Sets the time after which the SAS will no longer work. + * + * @param expiryTime When the SAS will no longer work + * + * @return The updated {@link TableServiceSasSignatureValues} object. + */ + public TableServiceSasSignatureValues setExpiryTime(OffsetDateTime expiryTime) { + this.expiryTime = expiryTime; + + return this; + } + + /** + * @return The permissions string allowed by the SAS. Please refer to {@link TableServiceSasPermission} for help + * determining the permissions allowed. + */ + public String getPermissions() { + return permissions; + } + + /** + * Sets the permissions string allowed by the SAS. Please refer to {@link TableServiceSasPermission} for help constructing + * the permissions string. + * + * @param permissions Permissions for the SAS + * + * @return The updated {@link TableServiceSasSignatureValues} object. + * + * @throws NullPointerException if {@code permissions} is null. + */ + public TableServiceSasSignatureValues setPermissions(TableServiceSasPermission permissions) { + Objects.requireNonNull(permissions, "'permissions' cannot be null"); + + this.permissions = permissions.toString(); + + return this; + } + + /** + * @return The {@link TableSasIpRange} which determines the IP ranges that are allowed to use the SAS. + */ + public TableSasIpRange getSasIpRange() { + return sasIpRange; + } + + /** + * Sets the {@link TableSasIpRange} which determines the IP ranges that are allowed to use the SAS. + * + * @param sasIpRange Allowed IP range to set + * + * @return The updated {@link TableServiceSasSignatureValues} object. + * + * @see Specifying + * IP Address or IP range + */ + public TableServiceSasSignatureValues setSasIpRange(TableSasIpRange sasIpRange) { + this.sasIpRange = sasIpRange; + + return this; + } + + /** + * @return The name of the access policy on the table this SAS references if any. Please see + * here + * for more information. + */ + public String getIdentifier() { + return identifier; + } + + /** + * Sets the name of the access policy on the table this SAS references if any. Please see + * here + * for more information. + * + * @param identifier Name of the access policy + * + * @return The updated {@link TableServiceSasSignatureValues} object. + */ + public TableServiceSasSignatureValues setIdentifier(String identifier) { + this.identifier = identifier; + + return this; + } + + /** + * Get the minimum partition key accessible with this shared access signature. Key values are inclusive. If omitted, + * there is no lower bound on the table entities that can be accessed. If provided, it must accompany a start row + * key that can be set via {@code setStartRowKey()}. + * + * @return The start partition key. + */ + public String getStartPartitionKey() { + return this.startPartitionKey; + } + + /** + * Set the minimum partition key accessible with this shared access signature. Key values are inclusive. If omitted, + * there is no lower bound on the table entities that can be accessed. If provided, it must accompany a start row + * key that can be set via {@code setStartRowKey()}. + * + * @param startPartitionKey The start partition key to set. + * + * @return The updated {@link TableServiceSasSignatureValues} object. + */ + public TableServiceSasSignatureValues setStartPartitionKey(String startPartitionKey) { + this.startPartitionKey = startPartitionKey; + return this; + } + + /** + * Get the minimum row key accessible with this shared access signature. Key values are inclusive. If omitted, there + * is no lower bound on the table entities that can be accessed. If provided, it must accompany a start row key + * that can be set via {@code setStartPartitionKey()}. + * + * @return The start row key. + */ + public String getStartRowKey() { + return this.startRowKey; + } + + /** + * Set the minimum row key accessible with this shared access signature. Key values are inclusive. If omitted, there + * is no lower bound on the table entities that can be accessed. If provided, it must accompany a start row key + * that can be set via {@code setStartPartitionKey()}. + * + * @param startRowKey The start row key to set. + * + * @return The updated {@link TableServiceSasSignatureValues} object. + */ + public TableServiceSasSignatureValues setStartRowKey(String startRowKey) { + this.startRowKey = startRowKey; + + return this; + } + + /** + * Get the maximum partition key accessible with this shared access signature. Key values are inclusive. If omitted, + * there is no upper bound on the table entities that can be accessed. If provided, it must accompany an ending row + * key that can be set via {@code setEndRowKey()}. + * + * @return The end partition key. + */ + public String getEndPartitionKey() { + return this.endPartitionKey; + } + + /** + * Set the maximum partition key accessible with this shared access signature. Key values are inclusive. If omitted, + * there is no upper bound on the table entities that can be accessed. If provided, it must accompany an ending row + * key that can be set via {@code setEndRowKey()}. + * + * @param endPartitionKey The end partition key to set. + * + * @return The updated {@link TableServiceSasSignatureValues} object. + */ + public TableServiceSasSignatureValues setEndPartitionKey(String endPartitionKey) { + this.endPartitionKey = endPartitionKey; + + return this; + } + + /** + * Get the maximum row key accessible with this shared access signature. Key values are inclusive. If omitted, there + * is no upper bound on the table entities that can be accessed. If provided, it must accompany an ending row key + * that can be set via {@code setEndPartitionKey()}. + * + * @return The end row key. + */ + public String getEndRowKey() { + return this.endRowKey; + } + + /** + * Set the maximum row key accessible with this shared access signature. Key values are inclusive. If omitted, there + * is no upper bound on the table entities that can be accessed. If provided, it must accompany an ending row key + * that can be set via {@code setEndPartitionKey()}. + * + * @param endRowKey The end row key to set. + * + * @return The updated {@link TableServiceSasSignatureValues} object. + */ + public TableServiceSasSignatureValues setEndRowKey(String endRowKey) { + this.endRowKey = endRowKey; + + return this; + } +} diff --git a/sdk/tables/azure-data-tables/src/main/java/module-info.java b/sdk/tables/azure-data-tables/src/main/java/module-info.java index 1b8c7dec7b826..c96a67ca4840f 100644 --- a/sdk/tables/azure-data-tables/src/main/java/module-info.java +++ b/sdk/tables/azure-data-tables/src/main/java/module-info.java @@ -7,6 +7,7 @@ // public API surface area exports com.azure.data.tables; exports com.azure.data.tables.models; + exports com.azure.data.tables.sas to com.azure.core; exports com.azure.data.tables.implementation to com.azure.core; exports com.azure.data.tables.implementation.models to com.azure.core; @@ -16,4 +17,5 @@ opens com.azure.data.tables.implementation to com.fasterxml.jackson.databind, com.azure.core; opens com.azure.data.tables.implementation.models to com.fasterxml.jackson.databind, com.azure.core; opens com.azure.data.tables.models to com.fasterxml.jackson.databind, com.azure.core; + opens com.azure.data.tables.sas to com.fasterxml.jackson.databind, com.azure.core; } From d4188f1d4ac5e658ef194ff5ae1ced30acf7d621 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 28 May 2021 20:27:29 -0500 Subject: [PATCH 02/25] Added partition key and row key values for SAS generation. --- .../implementation/StorageConstants.java | 20 +++++++++++++++++++ .../TableServiceSasGenerator.java | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java index 8cd2afb770ea2..631fedf9fd012 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java @@ -383,5 +383,25 @@ private UrlConstants() { * The SAS queue constant. */ public static final String SAS_QUEUE_CONSTANT = "q"; + + /** + * The SAS table start partition key. + */ + public static final String SAS_TABLE_START_PARTITION_KEY = "spk"; + + /** + * The SAS table start row key. + */ + public static final String SAS_TABLE_START_ROW_KEY = "srk"; + + /** + * The SAS table end partition key. + */ + public static final String SAS_TABLE_END_PARTITION_KEY = "epk"; + + /** + * The SAS table end row key. + */ + public static final String SAS_TABLE_END_ROW_KEY = "erk"; } } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java index 7126db6b7b58b..3b539ef3013be 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java @@ -100,6 +100,10 @@ private String encode(String signature) { tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNED_IDENTIFIER, this.identifier); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNATURE, signature); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_TABLE_START_PARTITION_KEY, startPartitionKey); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_TABLE_START_ROW_KEY, startRowKey); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_TABLE_END_PARTITION_KEY, endPartitionKey); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_TABLE_END_ROW_KEY, endRowKey); return sb.toString(); } From 0872d7e8127ff667c30380323e3cb4e226c69bbd Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 28 May 2021 22:52:33 -0500 Subject: [PATCH 03/25] Fixed CheckStyle issues. --- .../sas/TableAccountSasSignatureValues.java | 86 +++++++++---------- .../azure/data/tables/sas/package-info.java | 7 ++ 2 files changed, 50 insertions(+), 43 deletions(-) create mode 100644 sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/package-info.java diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java index fe941b4be0c83..6f7fc7e2db600 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java @@ -18,14 +18,14 @@ * @see Create an account SAS */ public final class TableAccountSasSignatureValues { + private final OffsetDateTime expiryTime; + private final String permissions; + private final String services; + private final String resourceTypes; private String version; private TableSasProtocol protocol; private OffsetDateTime startTime; - private OffsetDateTime expiryTime; - private String permissions; private TableSasIpRange sasIpRange; - private String services; - private String resourceTypes; /** * Initializes a new {@link TableAccountSasSignatureValues} object. @@ -49,6 +49,45 @@ public TableAccountSasSignatureValues(OffsetDateTime expiryTime, TableAccountSas this.permissions = permissions.toString(); } + /** + * Get The time after which the SAS will no longer work. + * + * @return The time after which the SAS will no longer work. + */ + public OffsetDateTime getExpiryTime() { + return expiryTime; + } + + /** + * Gets the operations the SAS user may perform. Please refer to {@link TableAccountSasPermission} to help determine + * which permissions are allowed. + * + * @return The operations the SAS user may perform. + */ + public String getPermissions() { + return permissions; + } + + /** + * Get the services accessible with this SAS. Please refer to {@link TableAccountSasService} to help determine which + * services are accessible. + * + * @return The services accessible with this SAS. + */ + public String getServices() { + return services; + } + + /** + * Get the resource types accessible with this SAS. Please refer to {@link TableAccountSasResourceType} to help determine + * the resource types that are accessible. + * + * @return The resource types accessible with this SAS. + */ + public String getResourceTypes() { + return resourceTypes; + } + /** * Get the service version that is targeted, if {@code null} or empty the latest service version targeted by the * library will be used. @@ -117,25 +156,6 @@ public TableAccountSasSignatureValues setStartTime(OffsetDateTime startTime) { return this; } - /** - * Get The time after which the SAS will no longer work. - * - * @return The time after which the SAS will no longer work. - */ - public OffsetDateTime getExpiryTime() { - return expiryTime; - } - - /** - * Gets the operations the SAS user may perform. Please refer to {@link TableAccountSasPermission} to help determine - * which permissions are allowed. - * - * @return The operations the SAS user may perform. - */ - public String getPermissions() { - return permissions; - } - /** * Get the {@link TableSasIpRange} which determines the IP ranges that are allowed to use the SAS. * @@ -161,26 +181,6 @@ public TableAccountSasSignatureValues setSasIpRange(TableSasIpRange sasIpRange) return this; } - /** - * Get the services accessible with this SAS. Please refer to {@link TableAccountSasService} to help determine which - * services are accessible. - * - * @return The services accessible with this SAS. - */ - public String getServices() { - return services; - } - - /** - * Get the resource types accessible with this SAS. Please refer to {@link TableAccountSasResourceType} to help determine - * the resource types that are accessible. - * - * @return The resource types accessible with this SAS. - */ - public String getResourceTypes() { - return resourceTypes; - } - private String stringToSign(final AzureNamedKeyCredential azureNamedKeyCredential) { return String.join("\n", azureNamedKeyCredential.getAzureNamedKey().getName(), diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/package-info.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/package-info.java new file mode 100644 index 0000000000000..bc32f4bf29286 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/package-info.java @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * Package containing SAS (shared access signature) classes used by Azure Data Tables. + */ +package com.azure.data.tables.sas; From 5e401ed0fe816c3f2489cf7dfb7da379e25cdb86 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 28 May 2021 23:22:23 -0500 Subject: [PATCH 04/25] Fixed SpotBugs issue. --- .../sas/TableAccountSasSignatureValues.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java index 6f7fc7e2db600..bd73faab649d0 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java @@ -180,19 +180,4 @@ public TableAccountSasSignatureValues setSasIpRange(TableSasIpRange sasIpRange) return this; } - - private String stringToSign(final AzureNamedKeyCredential azureNamedKeyCredential) { - return String.join("\n", - azureNamedKeyCredential.getAzureNamedKey().getName(), - TableAccountSasPermission.parse(this.permissions).toString(), // guarantees ordering - this.services, - resourceTypes, - this.startTime == null ? "" : StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), - StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), - this.sasIpRange == null ? "" : this.sasIpRange.toString(), - this.protocol == null ? "" : this.protocol.toString(), - this.version, - "" // Account SAS requires an additional newline character - ); - } } From aa9ec312921a6fae168ec0d14d05b24a56a06366 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Sat, 29 May 2021 01:13:58 -0500 Subject: [PATCH 05/25] Removed more unused imports. --- .../azure/data/tables/sas/TableAccountSasSignatureValues.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java index bd73faab649d0..00281dfa1b9fd 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java @@ -2,9 +2,6 @@ // Licensed under the MIT License. package com.azure.data.tables.sas; -import com.azure.core.credential.AzureNamedKeyCredential; -import com.azure.data.tables.implementation.StorageConstants; - import java.time.OffsetDateTime; import java.util.Objects; From 4144ec23f2b3549bfcb955ec2d1d4d49752aa310 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Tue, 1 Jun 2021 19:19:35 -0500 Subject: [PATCH 06/25] Renamed classes used for generating table-level SAS tokens. Made clients throw an exception when trying to generate SAS tokens if not authenticated with an AzureNamedKeyCredential. --- .../azure/data/tables/TableAsyncClient.java | 39 ++++++---- .../com/azure/data/tables/TableClient.java | 28 ++++--- .../data/tables/TableServiceAsyncClient.java | 16 +++- .../azure/data/tables/TableServiceClient.java | 6 ++ .../TableAccountSasGenerator.java | 59 ++++++++------- ...sGenerator.java => TableSasGenerator.java} | 75 ++++++++++--------- .../tables/sas/TableAccountSasPermission.java | 8 +- .../sas/TableAccountSasResourceType.java | 8 +- .../tables/sas/TableAccountSasService.java | 8 +- .../sas/TableAccountSasSignatureValues.java | 3 + ...ermission.java => TableSasPermission.java} | 38 +++++----- ...lues.java => TableSasSignatureValues.java} | 59 ++++++++------- 12 files changed, 197 insertions(+), 150 deletions(-) rename sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/{TableServiceSasGenerator.java => TableSasGenerator.java} (81%) rename sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/{TableServiceSasPermission.java => TableSasPermission.java} (81%) rename sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/{TableServiceSasSignatureValues.java => TableSasSignatureValues.java} (80%) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java index a4f569550832a..cc4e4a60012da 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java @@ -23,8 +23,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.TableSasGenerator; 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; @@ -52,7 +52,7 @@ 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 com.azure.data.tables.sas.TableSasSignatureValues; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -200,33 +200,46 @@ public TableServiceVersion getServiceVersion() { } /** - * Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}. + * Generates a service SAS for the table using the specified {@link TableSasSignatureValues}. * *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} - *

See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.

+ *

See {@link TableSasSignatureValues} for more information on how to construct a service SAS.

* - * @param tableServiceSasSignatureValues {@link TableServiceSasSignatureValues} + * @param tableSasSignatureValues {@link TableSasSignatureValues} * * @return A {@code String} representing the SAS query parameters. + * + * @throws IllegalStateException If this {@link TableAsyncClient} is not authenticated with an + * {@link AzureNamedKeyCredential}. */ - public String generateSas(TableServiceSasSignatureValues tableServiceSasSignatureValues) { - return generateSas(tableServiceSasSignatureValues, Context.NONE); + public String generateSas(TableSasSignatureValues tableSasSignatureValues) { + return generateSas(tableSasSignatureValues, Context.NONE); } /** - * Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}. + * Generates a service SAS for the table using the specified {@link TableSasSignatureValues}. * *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} - *

See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.

+ *

See {@link TableSasSignatureValues} for more information on how to construct a service SAS.

* - * @param tableServiceSasSignatureValues {@link TableServiceSasSignatureValues} + * @param tableSasSignatureValues {@link TableSasSignatureValues} * @param context Additional context that is passed through the code when generating a SAS. * * @return A {@code String} representing the SAS query parameters. + * + * @throws IllegalStateException If this {@link TableAsyncClient} is not authenticated with an + * {@link AzureNamedKeyCredential}. */ - public String generateSas(TableServiceSasSignatureValues tableServiceSasSignatureValues, Context context) { - return new TableServiceSasGenerator(tableServiceSasSignatureValues, getTableName()) - .generateSas(TableSasUtils.extractNamedKeyCredential(getHttpPipeline()), context); + public String generateSas(TableSasSignatureValues tableSasSignatureValues, Context context) { + AzureNamedKeyCredential azureNamedKeyCredential = TableSasUtils.extractNamedKeyCredential(getHttpPipeline()); + + if (azureNamedKeyCredential == null) { + throw logger.logExceptionAsError(new IllegalStateException("Cannot generate a SAS token with a client that" + + " is not authenticated with an AzureNamedKeyCredential.")); + } + + return new TableSasGenerator(tableSasSignatureValues, getTableName(), azureNamedKeyCredential, context) + .getSas(); } /** diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java index a8f90f2fded96..fca2b53745a17 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java @@ -19,7 +19,7 @@ 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 com.azure.data.tables.sas.TableSasSignatureValues; import java.time.Duration; import java.util.List; @@ -83,32 +83,38 @@ public TableServiceVersion getServiceVersion() { } /** - * Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}. + * Generates a service SAS for the table using the specified {@link TableSasSignatureValues}. * *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} - *

See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.

+ *

See {@link TableSasSignatureValues} for more information on how to construct a service SAS.

* - * @param tableServiceSasSignatureValues {@link TableServiceSasSignatureValues} + * @param tableSasSignatureValues {@link TableSasSignatureValues} * * @return A {@code String} representing the SAS query parameters. + * + * @throws IllegalStateException If this {@link TableClient} is not authenticated with an + * {@link AzureNamedKeyCredential}. */ - public String generateSas(TableServiceSasSignatureValues tableServiceSasSignatureValues) { - return client.generateSas(tableServiceSasSignatureValues, Context.NONE); + public String generateSas(TableSasSignatureValues tableSasSignatureValues) { + return client.generateSas(tableSasSignatureValues, Context.NONE); } /** - * Generates a service SAS for the table using the specified {@link TableServiceSasSignatureValues}. + * Generates a service SAS for the table using the specified {@link TableSasSignatureValues}. * *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} - *

See {@link TableServiceSasSignatureValues} for more information on how to construct a service SAS.

+ *

See {@link TableSasSignatureValues} for more information on how to construct a service SAS.

* - * @param tableServiceSasSignatureValues {@link TableServiceSasSignatureValues} + * @param tableSasSignatureValues {@link TableSasSignatureValues} * @param context Additional context that is passed through the code when generating a SAS. * * @return A {@code String} representing the SAS query parameters. + * + * @throws IllegalStateException If this {@link TableClient} is not authenticated with an + * {@link AzureNamedKeyCredential}. */ - public String generateSas(TableServiceSasSignatureValues tableServiceSasSignatureValues, Context context) { - return client.generateSas(tableServiceSasSignatureValues, context); + public String generateSas(TableSasSignatureValues tableSasSignatureValues, Context context) { + return client.generateSas(tableSasSignatureValues, context); } /** diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java index 008b7df7bb2d4..b750d8d8d45e1 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java @@ -161,6 +161,9 @@ public TableServiceVersion getServiceVersion() { * @param tableAccountSasSignatureValues {@link TableAccountSasSignatureValues}. * * @return A {@link String} representing the SAS query parameters. + * + * @throws IllegalStateException If this {@link TableClient} is not authenticated with an + * {@link AzureNamedKeyCredential}. */ public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues) { return generateAccountSas(tableAccountSasSignatureValues, Context.NONE); @@ -177,10 +180,19 @@ public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasS * @param context Additional context that is passed through the code when generating a SAS. * * @return A {@link String} representing the SAS query parameters. + * + * @throws IllegalStateException If this {@link TableClient} is not authenticated with an + * {@link AzureNamedKeyCredential}. */ public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues, Context context) { - return new TableAccountSasGenerator(tableAccountSasSignatureValues) - .generateSas(TableSasUtils.extractNamedKeyCredential(getHttpPipeline()), context); + AzureNamedKeyCredential azureNamedKeyCredential = TableSasUtils.extractNamedKeyCredential(getHttpPipeline()); + + if (azureNamedKeyCredential == null) { + throw logger.logExceptionAsError(new IllegalStateException("Cannot generate a SAS token with a client that" + + " is not authenticated with an AzureNamedKeyCredential.")); + } + + return new TableAccountSasGenerator(tableAccountSasSignatureValues, azureNamedKeyCredential, context).getSas(); } /** diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java index 0835771731d8a..df7f39ce97a9c 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java @@ -83,6 +83,9 @@ public TableServiceVersion getServiceVersion() { * @param tableAccountSasSignatureValues {@link TableAccountSasSignatureValues}. * * @return A {@link String} representing the SAS query parameters. + * + * @throws IllegalStateException If this {@link TableClient} is not authenticated with an + * {@link AzureNamedKeyCredential}. */ public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues) { return client.generateAccountSas(tableAccountSasSignatureValues); @@ -99,6 +102,9 @@ public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasS * @param context Additional context that is passed through the code when generating a SAS. * * @return A {@link String} representing the SAS query parameters. + * + * @throws IllegalStateException If this {@link TableClient} is not authenticated with an + * {@link AzureNamedKeyCredential}. */ public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues, Context context) { return client.generateAccountSas(tableAccountSasSignatureValues, context); diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java index 5405884690a29..fd5ab0e8940f1 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java @@ -24,21 +24,34 @@ */ public class TableAccountSasGenerator { private final ClientLogger logger = new ClientLogger(TableAccountSasGenerator.class); + private final OffsetDateTime expiryTime; + private final OffsetDateTime startTime; + private final String permissions; + private final String resourceTypes; + private final String services; + private final String sas; + private final TableSasProtocol protocol; + private final TableSasIpRange sasIpRange; private String version; - private TableSasProtocol protocol; - private OffsetDateTime startTime; - private OffsetDateTime expiryTime; - private String permissions; - private TableSasIpRange sasIpRange; - private String services; - private String resourceTypes; /** - * Creates a new {@link TableAccountSasGenerator} with the specified parameters. + * Creates a new {@link TableAccountSasGenerator} which will generate an account-level SAS signed with an + * {@link AzureNamedKeyCredential}. * * @param sasValues The {@link TableAccountSasSignatureValues account signature values}. + * @param azureNamedKeyCredential An {@link AzureNamedKeyCredential} whose key will be used to sign the SAS. + * @param context Additional context that is passed through the code when generating a SAS. */ - public TableAccountSasGenerator(TableAccountSasSignatureValues sasValues) { + public TableAccountSasGenerator(TableAccountSasSignatureValues sasValues, + AzureNamedKeyCredential azureNamedKeyCredential, + Context context) { + Objects.requireNonNull(sasValues, "'sasValues' cannot be null."); + Objects.requireNonNull(azureNamedKeyCredential, "'azureNamedKeyCredential' cannot be null."); + Objects.requireNonNull(sasValues.getServices(), "'services' in 'sasValues' cannot be null."); + Objects.requireNonNull(sasValues.getResourceTypes(), "'resourceTypes' in 'sasValues' cannot be null."); + Objects.requireNonNull(sasValues.getExpiryTime(), "'expiryTime' in 'sasValues' cannot be null."); + Objects.requireNonNull(sasValues.getPermissions(), "'permissions' in 'sasValues' cannot be null."); + this.version = sasValues.getVersion(); this.protocol = sasValues.getProtocol(); this.startTime = sasValues.getStartTime(); @@ -47,32 +60,28 @@ public TableAccountSasGenerator(TableAccountSasSignatureValues sasValues) { this.sasIpRange = sasValues.getSasIpRange(); this.services = sasValues.getServices(); this.resourceTypes = sasValues.getResourceTypes(); - } - - /** - * Generates a Sas signed with a {@link AzureNamedKeyCredential}. - * - * @param azureNamedKeyCredential {@link AzureNamedKeyCredential} - * @param context Additional context that is passed through the code when generating a SAS. - * @return A String representing the Sas - */ - public String generateSas(AzureNamedKeyCredential azureNamedKeyCredential, Context context) { - Objects.requireNonNull(azureNamedKeyCredential, "'azureNamedKeyCredential' cannot be null."); - Objects.requireNonNull(services, "'services' cannot be null."); - Objects.requireNonNull(resourceTypes, "'resourceTypes' cannot be null."); - Objects.requireNonNull(expiryTime, "'expiryTime' cannot be null."); - Objects.requireNonNull(permissions, "'permissions' cannot be null."); if (CoreUtils.isNullOrEmpty(version)) { version = StorageConstants.HeaderConstants.TARGET_STORAGE_VERSION; } + String stringToSign = stringToSign(azureNamedKeyCredential); + logStringToSign(logger, stringToSign, context); // Signature is generated on the un-url-encoded values. String signature = computeHMac256(azureNamedKeyCredential.getAzureNamedKey().getKey(), stringToSign); - return encode(signature); + this.sas = encode(signature); + } + + /** + * Get the SAS produced by this {@link TableAccountSasGenerator}. + * + * @return The SAS produced by this {@link TableAccountSasGenerator}. + */ + public String getSas() { + return sas; } private String stringToSign(final AzureNamedKeyCredential azureNamedKeyCredential) { diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java similarity index 81% rename from sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java rename to sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java index 3b539ef3013be..74b73797213b4 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableServiceSasGenerator.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java @@ -7,9 +7,9 @@ import com.azure.core.util.logging.ClientLogger; import com.azure.data.tables.TableServiceVersion; import com.azure.data.tables.sas.TableSasIpRange; +import com.azure.data.tables.sas.TableSasPermission; import com.azure.data.tables.sas.TableSasProtocol; -import com.azure.data.tables.sas.TableServiceSasPermission; -import com.azure.data.tables.sas.TableServiceSasSignatureValues; +import com.azure.data.tables.sas.TableSasSignatureValues; import java.time.OffsetDateTime; import java.util.Objects; @@ -22,30 +22,36 @@ /** * A class containing utility methods for generating SAS tokens for the Azure Data Tables service. */ -public class TableServiceSasGenerator { - private final ClientLogger logger = new ClientLogger(TableServiceSasGenerator.class); - - private String version; - private TableSasProtocol protocol; - private OffsetDateTime startTime; - private OffsetDateTime expiryTime; +public class TableSasGenerator { + private final ClientLogger logger = new ClientLogger(TableSasGenerator.class); + private final OffsetDateTime expiryTime; + private final OffsetDateTime startTime; + private final String endPartitionKey; + private final String endRowKey; + private final String identifier; + private final String sas; + private final String startPartitionKey; + private final String startRowKey; + private final String tableName; + private final TableSasProtocol protocol; + private final TableSasIpRange sasIpRange; private String permissions; - private TableSasIpRange sasIpRange; - private String tableName; - private String identifier; - private String startPartitionKey; - private String startRowKey; - private String endPartitionKey; - private String endRowKey; + private String version; /** - * Creates a new {@link TableServiceSasGenerator} with the specified parameters + * Creates a new {@link TableSasGenerator} which will generate an table-level SAS signed with an + * {@link AzureNamedKeyCredential}. * - * @param sasValues The {@link TableServiceSasSignatureValues} to generate the SAS token with. + * @param sasValues The {@link TableSasSignatureValues} to generate the SAS token with. * @param tableName The table name. + * @param azureNamedKeyCredential An {@link AzureNamedKeyCredential} whose key will be used to sign the SAS. + * @param context Additional context that is passed through the code when generating a SAS. */ - public TableServiceSasGenerator(TableServiceSasSignatureValues sasValues, String tableName) { + public TableSasGenerator(TableSasSignatureValues sasValues, String tableName, + AzureNamedKeyCredential azureNamedKeyCredential, + Context context) { Objects.requireNonNull(sasValues, "'sasValues' cannot be null."); + Objects.requireNonNull(azureNamedKeyCredential, "'azureNamedKeyCredential' cannot be null."); this.version = sasValues.getVersion(); this.protocol = sasValues.getProtocol(); @@ -59,28 +65,27 @@ public TableServiceSasGenerator(TableServiceSasSignatureValues sasValues, String this.startRowKey = sasValues.getStartRowKey(); this.endPartitionKey = sasValues.getEndPartitionKey(); this.endRowKey = sasValues.getEndRowKey(); - } - - /** - * Generates a SAS signed with an {@link AzureNamedKeyCredential}. - * - * @param azureNamedKeyCredential {@link AzureNamedKeyCredential}. - * @param context Additional context that is passed through the code when generating a SAS. - * - * @return A {@link String} representing the SAS. - */ - public String generateSas(AzureNamedKeyCredential azureNamedKeyCredential, Context context) { - Objects.requireNonNull(azureNamedKeyCredential, "'azureNamedKeyCredential' cannot be null."); - ensureState(); + validateState(); // Signature is generated on the un-url-encoded values. String canonicalName = getCanonicalName(azureNamedKeyCredential.getAzureNamedKey().getName()); String stringToSign = stringToSign(canonicalName); + logStringToSign(logger, stringToSign, context); + String signature = computeHMac256(azureNamedKeyCredential.getAzureNamedKey().getKey(), stringToSign); - return encode(signature); + this.sas = encode(signature); + } + + /** + * Get the SAS produced by this {@link TableSasGenerator}. + * + * @return The SAS produced by this {@link TableSasGenerator}. + */ + public String getSas() { + return sas; } private String encode(String signature) { @@ -115,7 +120,7 @@ private String encode(String signature) { * 2. If there is no identifier set, ensure expiryTime and permissions are set. * 4. Re-parse permissions depending on what the resource is. If it is an unrecognised resource, do nothing. */ - private void ensureState() { + private void validateState() { if (version == null) { version = TableServiceVersion.getLatest().getVersion(); } @@ -129,7 +134,7 @@ private void ensureState() { if (permissions != null) { if (tableName != null) { - permissions = TableServiceSasPermission.parse(permissions).toString(); + permissions = TableSasPermission.parse(permissions).toString(); } else { // We won't re-parse the permissions if we don't know the type. logger.info("Not re-parsing permissions. Resource type is not table."); diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java index a5f2cb6b227f6..0297190352492 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java @@ -2,6 +2,7 @@ // Licensed under the MIT License. package com.azure.data.tables.sas; +import com.azure.core.annotation.Fluent; import com.azure.data.tables.implementation.StorageConstants; import java.util.Locale; @@ -20,6 +21,7 @@ * @see TableAccountSasSignatureValues * @see Create account SAS */ +@Fluent public final class TableAccountSasPermission { private boolean readPermission; private boolean addPermission; @@ -33,12 +35,6 @@ public final class TableAccountSasPermission { private boolean tagsPermission; private boolean filterTagsPermission; - /** - * Initializes an {@link TableAccountSasPermission} object with all fields set to {@code false}. - */ - public TableAccountSasPermission() { - } - /** * Creates an {@link TableAccountSasPermission} from the specified permissions string. This method will throw an * {@link IllegalArgumentException} if it encounters a character that does not correspond to a valid permission. diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java index e2a6c9059107c..05a565a140f14 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java @@ -2,6 +2,7 @@ // Licensed under the MIT License. package com.azure.data.tables.sas; +import com.azure.core.annotation.Fluent; import com.azure.data.tables.implementation.StorageConstants; import java.util.Locale; @@ -13,17 +14,12 @@ * {@link TableAccountSasSignatureValues} object. It is possible to construct the resources string without this class, * but the order of the resources is particular and this class guarantees correctness. */ +@Fluent public final class TableAccountSasResourceType { private boolean service; private boolean container; private boolean object; - /** - * Initializes an {@link TableAccountSasResourceType} object with all fields set to {@code false}. - */ - public TableAccountSasResourceType() { - } - /** * Creates an {@link TableAccountSasResourceType} from the specified resource types string. This method will throw an * {@link IllegalArgumentException} if it encounters a character that does not correspond to a valid resource type. diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java index 4cc45751b6ec5..3cf90b4e3d958 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java @@ -2,6 +2,7 @@ // Licensed under the MIT License. package com.azure.data.tables.sas; +import com.azure.core.annotation.Fluent; import com.azure.data.tables.implementation.StorageConstants; import java.util.Locale; @@ -13,18 +14,13 @@ * {@link TableAccountSasSignatureValues} object. It is possible to construct the services string without this class, but * the order of the services is particular and this class guarantees correctness. */ +@Fluent public final class TableAccountSasService { private boolean blob; private boolean file; private boolean queue; private boolean table; - /** - * Initializes an {@code AccountSasService} object with all fields set to {@code false}. - */ - public TableAccountSasService() { - } - /** * Creates an {@link TableAccountSasService} from the specified services string. This method will throw an * {@link IllegalArgumentException} if it encounters a character that does not correspond to a valid service. diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java index 00281dfa1b9fd..cef83db5cb9d6 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasSignatureValues.java @@ -2,6 +2,8 @@ // Licensed under the MIT License. package com.azure.data.tables.sas; +import com.azure.core.annotation.Fluent; + import java.time.OffsetDateTime; import java.util.Objects; @@ -14,6 +16,7 @@ * @see Storage SAS overview * @see Create an account SAS */ +@Fluent public final class TableAccountSasSignatureValues { private final OffsetDateTime expiryTime; private final String permissions; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasPermission.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java similarity index 81% rename from sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasPermission.java rename to sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java index add6469b8cd0e..c1d714757b88c 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasPermission.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java @@ -2,6 +2,7 @@ // Licensed under the MIT License. package com.azure.data.tables.sas; +import com.azure.core.annotation.Fluent; import com.azure.data.tables.implementation.StorageConstants; import java.util.Locale; @@ -10,7 +11,7 @@ * Constructs a string representing the permissions granted by an Azure Service SAS to a table. Setting a value to true * means that any SAS which uses these permissions will grant permissions for that operation. Once all the values are * set, this should be serialized with {@link #toString() toString} and set as the permissions field on - * {@link TableServiceSasSignatureValues#setPermissions(TableServiceSasPermission)} TableSasSignatureValues}. + * {@link TableSasSignatureValues#setPermissions(TableSasPermission)} TableSasSignatureValues}. * *

* It is possible to construct the permissions string without this class, but the order of the permissions is @@ -19,32 +20,33 @@ * * @see * Permissions for a table - * @see TableServiceSasSignatureValues + * @see TableSasSignatureValues */ -public class TableServiceSasPermission { +@Fluent +public final class TableSasPermission { private boolean readPermission; private boolean addPermission; private boolean updatePermission; private boolean processPermission; /** - * Initializes a {@link TableServiceSasPermission} object with all fields set to {@code false}. + * Initializes a {@link TableSasPermission} object with all fields set to {@code false}. */ - public TableServiceSasPermission() { + public TableSasPermission() { } /** - * Creates a {@link TableServiceSasPermission} from the specified permissions string. This method will throw an + * Creates a {@link TableSasPermission} from the specified permissions string. This method will throw an * {@link IllegalArgumentException} if it encounters a character that does not correspond to a valid permission. * - * @param permString A {@link String} which represents the {@link TableServiceSasPermission}. + * @param permString A {@link String} which represents the {@link TableSasPermission}. * - * @return A {@link TableServiceSasPermission} generated from the given {@link String}. + * @return A {@link TableSasPermission} generated from the given {@link String}. * * @throws IllegalArgumentException If {@code permString} contains a character other than r, a, u, or p. */ - public static TableServiceSasPermission parse(String permString) { - TableServiceSasPermission permissions = new TableServiceSasPermission(); + public static TableSasPermission parse(String permString) { + TableSasPermission permissions = new TableSasPermission(); for (int i = 0; i < permString.length(); i++) { char c = permString.charAt(i); @@ -92,7 +94,7 @@ public boolean hasReadPermission() { * * @return The updated TableSasPermission object. */ - public TableServiceSasPermission setReadPermission(boolean hasReadPermission) { + public TableSasPermission setReadPermission(boolean hasReadPermission) { this.readPermission = hasReadPermission; return this; @@ -117,9 +119,9 @@ public boolean hasAddPermission() { * Note: The {@code add} and {@code update} permissions are required for upsert operations. *

* - * @return The updated {@link TableServiceSasPermission} object. + * @return The updated {@link TableSasPermission} object. */ - public TableServiceSasPermission setAddPermission(boolean hasAddPermission) { + public TableSasPermission setAddPermission(boolean hasAddPermission) { this.addPermission = hasAddPermission; return this; @@ -144,9 +146,9 @@ public boolean hasUpdatePermission() { * @param hasUpdatePermission {@code true} if the SAS has permission to update entities in the table. {@code false}, * otherwise. * - * @return The updated {@link TableServiceSasPermission} object. + * @return The updated {@link TableSasPermission} object. */ - public TableServiceSasPermission setUpdatePermission(boolean hasUpdatePermission) { + public TableSasPermission setUpdatePermission(boolean hasUpdatePermission) { this.updatePermission = hasUpdatePermission; return this; @@ -167,9 +169,9 @@ public boolean hasProcessPermission() { * @param hasProcessPermission {@code true} if the SAS has permission to delete entities from the table. * {@code false}, otherwise. * - * @return The updated {@link TableServiceSasPermission} object. + * @return The updated {@link TableSasPermission} object. */ - public TableServiceSasPermission setProcessPermission(boolean hasProcessPermission) { + public TableSasPermission setProcessPermission(boolean hasProcessPermission) { this.processPermission = hasProcessPermission; return this; @@ -179,7 +181,7 @@ public TableServiceSasPermission setProcessPermission(boolean hasProcessPermissi * Converts the given permissions to a {@link String}. Using this method will guarantee the permissions are in an * order accepted by the service. * - * @return A {@link String} which represents the {@link TableServiceSasPermission}. + * @return A {@link String} which represents the {@link TableSasPermission}. */ @Override public String toString() { diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasSignatureValues.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java similarity index 80% rename from sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasSignatureValues.java rename to sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java index f7790187be521..39ecc13ecd277 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableServiceSasSignatureValues.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java @@ -2,6 +2,8 @@ // Licensed under the MIT License. package com.azure.data.tables.sas; +import com.azure.core.annotation.Fluent; + import java.time.OffsetDateTime; import java.util.Objects; @@ -14,7 +16,8 @@ * @see Storage SAS overview * @see Constructing a Service SAS */ -public class TableServiceSasSignatureValues { +@Fluent +public final class TableSasSignatureValues { private String version; private TableSasProtocol protocol; private OffsetDateTime startTime; @@ -31,9 +34,9 @@ public class TableServiceSasSignatureValues { * Creates an object with the specified expiry time and permissions. * * @param expiryTime The time after which the SAS will no longer work. - * @param permissions {@link TableServiceSasPermission table permissions} allowed by the SAS. + * @param permissions {@link TableSasPermission table permissions} allowed by the SAS. */ - public TableServiceSasSignatureValues(OffsetDateTime expiryTime, TableServiceSasPermission permissions) { + public TableSasSignatureValues(OffsetDateTime expiryTime, TableSasPermission permissions) { Objects.requireNonNull(expiryTime, "'expiryTime' cannot be null"); Objects.requireNonNull(permissions, "'permissions' cannot be null"); @@ -46,7 +49,7 @@ public TableServiceSasSignatureValues(OffsetDateTime expiryTime, TableServiceSas * * @param identifier Name of the access policy. */ - public TableServiceSasSignatureValues(String identifier) { + public TableSasSignatureValues(String identifier) { Objects.requireNonNull(identifier, "'identifier' cannot be null"); this.identifier = identifier; @@ -66,9 +69,9 @@ public String getVersion() { * * @param version Version to target * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. */ - public TableServiceSasSignatureValues setVersion(String version) { + public TableSasSignatureValues setVersion(String version) { this.version = version; return this; @@ -86,9 +89,9 @@ public TableSasProtocol getProtocol() { * * @param protocol Protocol for the SAS * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. */ - public TableServiceSasSignatureValues setProtocol(TableSasProtocol protocol) { + public TableSasSignatureValues setProtocol(TableSasProtocol protocol) { this.protocol = protocol; return this; @@ -106,9 +109,9 @@ public OffsetDateTime getStartTime() { * * @param startTime When the SAS takes effect * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. */ - public TableServiceSasSignatureValues setStartTime(OffsetDateTime startTime) { + public TableSasSignatureValues setStartTime(OffsetDateTime startTime) { this.startTime = startTime; return this; @@ -126,16 +129,16 @@ public OffsetDateTime getExpiryTime() { * * @param expiryTime When the SAS will no longer work * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. */ - public TableServiceSasSignatureValues setExpiryTime(OffsetDateTime expiryTime) { + public TableSasSignatureValues setExpiryTime(OffsetDateTime expiryTime) { this.expiryTime = expiryTime; return this; } /** - * @return The permissions string allowed by the SAS. Please refer to {@link TableServiceSasPermission} for help + * @return The permissions string allowed by the SAS. Please refer to {@link TableSasPermission} for help * determining the permissions allowed. */ public String getPermissions() { @@ -143,16 +146,16 @@ public String getPermissions() { } /** - * Sets the permissions string allowed by the SAS. Please refer to {@link TableServiceSasPermission} for help constructing + * Sets the permissions string allowed by the SAS. Please refer to {@link TableSasPermission} for help constructing * the permissions string. * * @param permissions Permissions for the SAS * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. * * @throws NullPointerException if {@code permissions} is null. */ - public TableServiceSasSignatureValues setPermissions(TableServiceSasPermission permissions) { + public TableSasSignatureValues setPermissions(TableSasPermission permissions) { Objects.requireNonNull(permissions, "'permissions' cannot be null"); this.permissions = permissions.toString(); @@ -172,12 +175,12 @@ public TableSasIpRange getSasIpRange() { * * @param sasIpRange Allowed IP range to set * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. * * @see Specifying * IP Address or IP range */ - public TableServiceSasSignatureValues setSasIpRange(TableSasIpRange sasIpRange) { + public TableSasSignatureValues setSasIpRange(TableSasIpRange sasIpRange) { this.sasIpRange = sasIpRange; return this; @@ -199,9 +202,9 @@ public String getIdentifier() { * * @param identifier Name of the access policy * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. */ - public TableServiceSasSignatureValues setIdentifier(String identifier) { + public TableSasSignatureValues setIdentifier(String identifier) { this.identifier = identifier; return this; @@ -225,9 +228,9 @@ public String getStartPartitionKey() { * * @param startPartitionKey The start partition key to set. * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. */ - public TableServiceSasSignatureValues setStartPartitionKey(String startPartitionKey) { + public TableSasSignatureValues setStartPartitionKey(String startPartitionKey) { this.startPartitionKey = startPartitionKey; return this; } @@ -250,9 +253,9 @@ public String getStartRowKey() { * * @param startRowKey The start row key to set. * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. */ - public TableServiceSasSignatureValues setStartRowKey(String startRowKey) { + public TableSasSignatureValues setStartRowKey(String startRowKey) { this.startRowKey = startRowKey; return this; @@ -276,9 +279,9 @@ public String getEndPartitionKey() { * * @param endPartitionKey The end partition key to set. * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. */ - public TableServiceSasSignatureValues setEndPartitionKey(String endPartitionKey) { + public TableSasSignatureValues setEndPartitionKey(String endPartitionKey) { this.endPartitionKey = endPartitionKey; return this; @@ -302,9 +305,9 @@ public String getEndRowKey() { * * @param endRowKey The end row key to set. * - * @return The updated {@link TableServiceSasSignatureValues} object. + * @return The updated {@link TableSasSignatureValues} object. */ - public TableServiceSasSignatureValues setEndRowKey(String endRowKey) { + public TableSasSignatureValues setEndRowKey(String endRowKey) { this.endRowKey = endRowKey; return this; From 4e85675e3955feea659147d42c515181d8b60ff1 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Tue, 1 Jun 2021 19:26:31 -0500 Subject: [PATCH 07/25] Made client builders throw an IllegalStateException if more than one authentication setting is applied. --- .../azure/data/tables/TableClientBuilder.java | 53 +++++++++++----- .../tables/TableServiceClientBuilder.java | 60 +++++++++++++------ 2 files changed, 82 insertions(+), 31 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java index 055caf2de4e59..f449b8688eca3 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java @@ -184,15 +184,16 @@ public TableClientBuilder configuration(Configuration configuration) { } /** - * 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)}. + * Sets the SAS token used to authorize requests sent to the service. Setting this is mutually exclusive with + * {@code credential(AzureSasCredential)} or {@code credential(AzureNamedKeyCredential)}. * * @param sasToken The SAS token to use for authenticating requests. * * @return The updated {@link TableClientBuilder}. * - * @throws IllegalArgumentException If {@code sasToken} is empty. * @throws NullPointerException If {@code sasToken} is {@code null}. + * @throws IllegalArgumentException If {@code sasToken} is empty. + * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableClientBuilder sasToken(String sasToken) { if (sasToken == null) { @@ -200,56 +201,80 @@ public TableClientBuilder sasToken(String sasToken) { } if (sasToken.isEmpty()) { - throw logger.logExceptionAsError(new IllegalArgumentException("'sasToken' cannot be null or empty.")); + throw logger.logExceptionAsError(new IllegalArgumentException("'sasToken' cannot be empty.")); + } + + if (this.azureNamedKeyCredential != null || this.azureSasCredential != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this 'sasToken'. A credential has already been set for this" + + " builder to be used for authentication.")); } this.sasToken = sasToken; - this.azureNamedKeyCredential = null; - this.azureSasCredential = null; return this; } /** - * 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)}. + * Sets the {@link AzureSasCredential} used to authorize requests sent to the service. Setting this is mutually + * exclusive with {@code credential(AzureNamedKeyCredential)} or {@code sasToken(String)}. * * @param credential {@link AzureSasCredential} used to authorize requests sent to the service. * * @return The updated {@link TableClientBuilder}. * * @throws NullPointerException If {@code credential} is {@code null}. + * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableClientBuilder credential(AzureSasCredential credential) { if (credential == null) { throw logger.logExceptionAsError(new NullPointerException("'credential' cannot be null.")); } + if (this.azureNamedKeyCredential != null) { + throw logger.logExceptionAsError( + new IllegalStateException("A credential of a different type has already been set for this builder.")); + } + + if (this.sasToken != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this credential. A SAS token has already been set for this" + + " builder to be used for authentication.")); + } + this.azureSasCredential = credential; - this.azureNamedKeyCredential = null; - this.sasToken = null; return this; } /** - * 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)}. + * Sets the {@link AzureNamedKeyCredential} used to authorize requests sent to the service. Setting this is mutually + * exclusive with using {@code credential(AzureSasCredential)} or {@code sasToken(String)}. * * @param credential {@link AzureNamedKeyCredential} used to authorize requests sent to the service. * * @return The updated {@link TableClientBuilder}. * * @throws NullPointerException If {@code credential} is {@code null}. + * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableClientBuilder credential(AzureNamedKeyCredential credential) { if (credential == null) { throw logger.logExceptionAsError(new NullPointerException("'credential' cannot be null.")); } + if (this.azureSasCredential != null) { + throw logger.logExceptionAsError( + new IllegalStateException("A credential of a different type has already been set for this builder.")); + } + + if (this.sasToken != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this credential. A SAS token has already been set for this" + + " builder to be used for authentication.")); + } + this.azureNamedKeyCredential = credential; - this.azureSasCredential = null; - this.sasToken = null; return this; } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java index 2de97a5505481..ee6b6ec66dc2a 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java @@ -177,14 +177,16 @@ public TableServiceClientBuilder configuration(Configuration configuration) { } /** - * 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)}. + * Sets the SAS token used to authorize requests sent to the service. Setting this is mutually exclusive with + * {@code credential(AzureSasCredential)} or {@code credential(AzureNamedKeyCredential)}. * * @param sasToken The SAS token to use for authenticating requests. * * @return The updated {@link TableServiceClientBuilder}. * - * @throws NullPointerException if {@code sasToken} is {@code null}. + * @throws NullPointerException If {@code sasToken} is {@code null}. + * @throws IllegalArgumentException If {@code sasToken} is empty. + * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableServiceClientBuilder sasToken(String sasToken) { if (sasToken == null) { @@ -192,56 +194,80 @@ public TableServiceClientBuilder sasToken(String sasToken) { } if (sasToken.isEmpty()) { - throw logger.logExceptionAsError(new IllegalArgumentException("'sasToken' cannot be null or empty.")); + throw logger.logExceptionAsError(new IllegalArgumentException("'sasToken' cannot be empty.")); + } + + if (this.azureNamedKeyCredential != null || this.azureSasCredential != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this 'sasToken'. A credential has already been set for this" + + " builder to be used for authentication.")); } this.sasToken = sasToken; - this.azureNamedKeyCredential = null; - this.azureSasCredential = null; return this; } /** - * 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)}. + * Sets the {@link AzureSasCredential} used to authorize requests sent to the service. Setting this is mutually + * exclusive with {@code credential(AzureNamedKeyCredential)} or {@code sasToken(String)}. * * @param credential {@link AzureSasCredential} used to authorize requests sent to the service. * * @return The updated {@link TableServiceClientBuilder}. * - * @throws NullPointerException if {@code credential} is {@code null}. + * @throws NullPointerException If {@code credential} is {@code null}. + * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableServiceClientBuilder credential(AzureSasCredential credential) { if (credential == null) { throw logger.logExceptionAsError(new NullPointerException("'credential' cannot be null.")); } + if (this.azureNamedKeyCredential != null) { + throw logger.logExceptionAsError( + new IllegalStateException("A credential of a different type has already been set for this builder.")); + } + + if (this.sasToken != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this credential. A SAS token has already been set for this" + + " builder to be used for authentication.")); + } + this.azureSasCredential = credential; - this.azureNamedKeyCredential = null; - this.sasToken = null; return this; } /** - * 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)}. + * Sets the {@link AzureNamedKeyCredential} used to authorize requests sent to the service. Setting this is mutually + * exclusive with using {@code credential(AzureSasCredential)} or {@code sasToken(String)}. * * @param credential {@link AzureNamedKeyCredential} used to authorize requests sent to the service. * * @return The updated {@link TableServiceClientBuilder}. * - * @throws NullPointerException if {@code credential} is {@code null}. + * @throws NullPointerException If {@code credential} is {@code null}. + * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableServiceClientBuilder credential(AzureNamedKeyCredential credential) { if (credential == null) { throw logger.logExceptionAsError(new NullPointerException("'credential' cannot be null.")); } + if (this.azureSasCredential != null) { + throw logger.logExceptionAsError( + new IllegalStateException("A credential of a different type has already been set for this builder.")); + } + + if (this.sasToken != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this credential. A SAS token has already been set for this" + + " builder to be used for authentication.")); + } + this.azureNamedKeyCredential = credential; - this.azureSasCredential = null; - this.sasToken = null; return this; } @@ -286,7 +312,7 @@ public TableServiceClientBuilder httpLogOptions(HttpLogOptions logOptions) { * * @return The updated {@link TableServiceClientBuilder}. * - * @throws NullPointerException if {@code pipelinePolicy} is {@code null}. + * @throws NullPointerException If {@code pipelinePolicy} is {@code null}. */ public TableServiceClientBuilder addPolicy(HttpPipelinePolicy pipelinePolicy) { if (pipelinePolicy == null) { From fa7eb989086836974cef38f1625b06539c5afe34 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Tue, 1 Jun 2021 19:27:03 -0500 Subject: [PATCH 08/25] Changed module-info.java to export the tables package to all other packages. --- sdk/tables/azure-data-tables/src/main/java/module-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/module-info.java b/sdk/tables/azure-data-tables/src/main/java/module-info.java index c96a67ca4840f..94f4c1eb8226b 100644 --- a/sdk/tables/azure-data-tables/src/main/java/module-info.java +++ b/sdk/tables/azure-data-tables/src/main/java/module-info.java @@ -7,7 +7,7 @@ // public API surface area exports com.azure.data.tables; exports com.azure.data.tables.models; - exports com.azure.data.tables.sas to com.azure.core; + exports com.azure.data.tables.sas; exports com.azure.data.tables.implementation to com.azure.core; exports com.azure.data.tables.implementation.models to com.azure.core; From 159c76af3c63adb170e9c19624b6c81a0e6a2cb7 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Wed, 2 Jun 2021 16:39:56 -0500 Subject: [PATCH 09/25] Added tests for SAS models. --- .../data/tables/sas/TableSasIpRange.java | 27 +- .../azure/data/tables/sas/SasModelsTest.java | 321 ++++++++++++++++++ 2 files changed, 336 insertions(+), 12 deletions(-) create mode 100644 sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/sas/SasModelsTest.java diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java index 37e412809fdd5..ba0d838ded794 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java @@ -11,21 +11,22 @@ public final class TableSasIpRange { private String ipMax; /** - * Constructs an SasIpRange object. + * Constructs an {@link TableSasIpRange} object. */ public TableSasIpRange() { } /** - * Creates a {@code SasIpRange} from the specified string. + * Creates a {@link TableSasIpRange} from the specified string. * - * @param rangeStr The {@code String} representation of the {@code SasIpRange}. - * @return The {@code SasIpRange} generated from the {@code String}. + * @param rangeStr The {@link String} representation of the {@link TableSasIpRange}. + * @return The {@link TableSasIpRange} generated from the {@link String}. */ public static TableSasIpRange parse(String rangeStr) { String[] addrs = rangeStr.split("-"); TableSasIpRange range = new TableSasIpRange().setIpMin(addrs[0]); + if (addrs.length > 1) { range.setIpMax(addrs[1]); } @@ -34,7 +35,7 @@ public static TableSasIpRange parse(String rangeStr) { } /** - * @return the minimum IP address of the range + * @return The minimum IP address of the range. */ public String getIpMin() { return ipMin; @@ -43,16 +44,17 @@ public String getIpMin() { /** * Sets the minimum IP address of the range. * - * @param ipMin IP address to set as the minimum - * @return the updated SasIpRange object + * @param ipMin IP address to set as the minimum. + * @return The updated {@link TableSasIpRange} object. */ public TableSasIpRange setIpMin(String ipMin) { this.ipMin = ipMin; + return this; } /** - * @return the maximum IP address of the range + * @return The maximum IP address of the range. */ public String getIpMax() { return ipMax; @@ -61,18 +63,19 @@ public String getIpMax() { /** * Sets the maximum IP address of the range. * - * @param ipMax IP address to set as the maximum - * @return the updated SasIpRange object + * @param ipMax IP address to set as the maximum. + * @return The updated {@link TableSasIpRange} object. */ public TableSasIpRange setIpMax(String ipMax) { this.ipMax = ipMax; + return this; } /** - * Output the single IP address or range of IP addresses for. + * Output the single IP address or range of IP addresses formatted as a {@link String}. * - * @return The single IP address or range of IP addresses formatted as a {@code String}. + * @return The single IP address or range of IP addresses formatted as a {@link String}. */ @Override public String toString() { diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/sas/SasModelsTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/sas/SasModelsTest.java new file mode 100644 index 0000000000000..518768c27e83d --- /dev/null +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/sas/SasModelsTest.java @@ -0,0 +1,321 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.data.tables.sas; + +import org.junit.jupiter.api.Test; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SasModelsTest { + @Test + public void create_tableAccountSasSignatureValues_WithMinimumValues() { + OffsetDateTime expiryTime = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + TableAccountSasPermission permissions = TableAccountSasPermission.parse("l"); + TableAccountSasService services = TableAccountSasService.parse("t"); + TableAccountSasResourceType resourceTypes = TableAccountSasResourceType.parse("o"); + + OffsetDateTime startTime = OffsetDateTime.of(2015, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + TableSasIpRange ipRange = TableSasIpRange.parse("a-b"); + TableSasProtocol protocol = TableSasProtocol.HTTPS_ONLY; + + TableAccountSasSignatureValues sasSignatureValues = + new TableAccountSasSignatureValues(expiryTime, permissions, services, resourceTypes) + .setStartTime(startTime) + .setSasIpRange(ipRange) + .setProtocol(protocol); + + assertEquals(expiryTime, sasSignatureValues.getExpiryTime()); + assertEquals(permissions.toString(), sasSignatureValues.getPermissions()); + assertEquals(services.toString(), sasSignatureValues.getServices()); + assertEquals(resourceTypes.toString(), sasSignatureValues.getResourceTypes()); + assertEquals(startTime, sasSignatureValues.getStartTime()); + assertEquals(ipRange, sasSignatureValues.getSasIpRange()); + assertEquals(protocol, sasSignatureValues.getProtocol()); + } + + @Test + public void create_tableAccountSasSignatureValues_WithNullRequiredValue() { + OffsetDateTime expiryTime = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + TableAccountSasPermission permissions = TableAccountSasPermission.parse("l"); // List permission + TableAccountSasService services = TableAccountSasService.parse("t"); // Tables service + TableAccountSasResourceType resourceTypes = TableAccountSasResourceType.parse("o"); // Object resource + + assertThrows(NullPointerException.class, + () -> new TableAccountSasSignatureValues(null, permissions, services, resourceTypes)); + assertThrows(NullPointerException.class, + () -> new TableAccountSasSignatureValues(expiryTime, null, services, resourceTypes)); + assertThrows(NullPointerException.class, + () -> new TableAccountSasSignatureValues(expiryTime, permissions, null, resourceTypes)); + assertThrows(NullPointerException.class, + () -> new TableAccountSasSignatureValues(expiryTime, permissions, services, null)); + } + + @Test + public void tableAccountSasPermission_toString() { + assertEquals("rwdxlacuptf", new TableAccountSasPermission() + .setReadPermission(true) + .setWritePermission(true) + .setDeletePermission(true) + .setListPermission(true) + .setAddPermission(true) + .setCreatePermission(true) + .setUpdatePermission(true) + .setProcessMessages(true) + .setDeleteVersionPermission(true) + .setTagsPermission(true) + .setFilterTagsPermission(true) + .toString()); + assertEquals("r", new TableAccountSasPermission().setReadPermission(true).toString()); + assertEquals("w", new TableAccountSasPermission().setWritePermission(true).toString()); + assertEquals("d", new TableAccountSasPermission().setDeletePermission(true).toString()); + assertEquals("l", new TableAccountSasPermission().setListPermission(true).toString()); + assertEquals("a", new TableAccountSasPermission().setAddPermission(true).toString()); + assertEquals("c", new TableAccountSasPermission().setCreatePermission(true).toString()); + assertEquals("u", new TableAccountSasPermission().setUpdatePermission(true).toString()); + assertEquals("p", new TableAccountSasPermission().setProcessMessages(true).toString()); + assertEquals("x", new TableAccountSasPermission().setDeleteVersionPermission(true).toString()); + assertEquals("t", new TableAccountSasPermission().setTagsPermission(true).toString()); + assertEquals("f", new TableAccountSasPermission().setFilterTagsPermission(true).toString()); + } + + @Test + public void tableAccountSasPermission_parse() { + TableAccountSasPermission tableAccountSasPermission = TableAccountSasPermission.parse("rwdxlacuptf"); + + assertTrue(tableAccountSasPermission.hasReadPermission()); + assertTrue(tableAccountSasPermission.hasWritePermission()); + assertTrue(tableAccountSasPermission.hasDeletePermission()); + assertTrue(tableAccountSasPermission.hasListPermission()); + assertTrue(tableAccountSasPermission.hasAddPermission()); + assertTrue(tableAccountSasPermission.hasCreatePermission()); + assertTrue(tableAccountSasPermission.hasUpdatePermission()); + assertTrue(tableAccountSasPermission.hasProcessMessages()); + assertTrue(tableAccountSasPermission.hasDeleteVersionPermission()); + assertTrue(tableAccountSasPermission.hasTagsPermission()); + assertTrue(tableAccountSasPermission.hasFilterTagsPermission()); + + tableAccountSasPermission = TableAccountSasPermission.parse("lwfrutpcaxd"); + + assertTrue(tableAccountSasPermission.hasReadPermission()); + assertTrue(tableAccountSasPermission.hasWritePermission()); + assertTrue(tableAccountSasPermission.hasDeletePermission()); + assertTrue(tableAccountSasPermission.hasListPermission()); + assertTrue(tableAccountSasPermission.hasAddPermission()); + assertTrue(tableAccountSasPermission.hasCreatePermission()); + assertTrue(tableAccountSasPermission.hasUpdatePermission()); + assertTrue(tableAccountSasPermission.hasProcessMessages()); + assertTrue(tableAccountSasPermission.hasDeleteVersionPermission()); + assertTrue(tableAccountSasPermission.hasTagsPermission()); + assertTrue(tableAccountSasPermission.hasFilterTagsPermission()); + + assertTrue(TableAccountSasPermission.parse("r").hasReadPermission()); + assertTrue(TableAccountSasPermission.parse("w").hasWritePermission()); + assertTrue(TableAccountSasPermission.parse("d").hasDeletePermission()); + assertTrue(TableAccountSasPermission.parse("l").hasListPermission()); + assertTrue(TableAccountSasPermission.parse("a").hasAddPermission()); + assertTrue(TableAccountSasPermission.parse("c").hasCreatePermission()); + assertTrue(TableAccountSasPermission.parse("u").hasUpdatePermission()); + assertTrue(TableAccountSasPermission.parse("p").hasProcessMessages()); + assertTrue(TableAccountSasPermission.parse("x").hasDeleteVersionPermission()); + assertTrue(TableAccountSasPermission.parse("t").hasTagsPermission()); + assertTrue(TableAccountSasPermission.parse("f").hasFilterTagsPermission()); + } + + @Test + public void tableAccountSasPermission_parse_illegalString() { + assertThrows(IllegalArgumentException.class, () -> TableAccountSasPermission.parse("rwaq")); + } + + @Test + public void tableAccountSasResourceType_toString() { + assertEquals("sco", new TableAccountSasResourceType() + .setService(true) + .setContainer(true) + .setObject(true) + .toString()); + + assertEquals("s", new TableAccountSasResourceType().setService(true).toString()); + assertEquals("c", new TableAccountSasResourceType().setContainer(true).toString()); + assertEquals("o", new TableAccountSasResourceType().setObject(true).toString()); + } + + @Test + public void tableAccountSasResourceType_parse() { + TableAccountSasResourceType tableAccountSasResourceType = TableAccountSasResourceType.parse("sco"); + + assertTrue(tableAccountSasResourceType.isService()); + assertTrue(tableAccountSasResourceType.isContainer()); + assertTrue(tableAccountSasResourceType.isObject()); + + assertTrue(TableAccountSasResourceType.parse("s").isService()); + assertTrue(TableAccountSasResourceType.parse("c").isContainer()); + assertTrue(TableAccountSasResourceType.parse("o").isObject()); + } + + @Test + public void tableAccountSasResourceType_parse_illegalString() { + assertThrows(IllegalArgumentException.class, () -> TableAccountSasResourceType.parse("scq")); + } + + @Test + public void tableAccountSasService_toString() { + assertEquals("bqtf", new TableAccountSasService() + .setBlobAccess(true) + .setQueueAccess(true) + .setTableAccess(true) + .setFileAccess(true) + .toString()); + + assertEquals("b", new TableAccountSasService().setBlobAccess(true).toString()); + assertEquals("q", new TableAccountSasService().setQueueAccess(true).toString()); + assertEquals("t", new TableAccountSasService().setTableAccess(true).toString()); + assertEquals("f", new TableAccountSasService().setFileAccess(true).toString()); + } + + @Test + public void tableAccountSasService_parse() { + TableAccountSasService tableAccountSasService = TableAccountSasService.parse("bqtf"); + + assertTrue(tableAccountSasService.hasBlobAccess()); + assertTrue(tableAccountSasService.hasQueueAccess()); + assertTrue(tableAccountSasService.hasTableAccess()); + assertTrue(tableAccountSasService.hasFileAccess()); + + assertTrue(TableAccountSasService.parse("b").hasBlobAccess()); + assertTrue(TableAccountSasService.parse("q").hasQueueAccess()); + assertTrue(TableAccountSasService.parse("t").hasTableAccess()); + assertTrue(TableAccountSasService.parse("f").hasFileAccess()); + } + + @Test + public void tableAccountSasService_parse_illegalString() { + assertThrows(IllegalArgumentException.class, () -> TableAccountSasService.parse("bqta")); + } + + @Test + public void tableSasIpRange_toString() { + assertEquals("a-b", new TableSasIpRange() + .setIpMin("a") + .setIpMax("b") + .toString()); + + assertEquals("a", new TableSasIpRange().setIpMin("a").toString()); + assertEquals("", new TableSasIpRange().setIpMax("b").toString()); + } + + @Test + public void tableSasIpRange_parse() { + TableSasIpRange tableSasIpRange = TableSasIpRange.parse("a-b"); + + assertEquals("a", tableSasIpRange.getIpMin()); + assertEquals("b", tableSasIpRange.getIpMax()); + + tableSasIpRange = TableSasIpRange.parse("a"); + + assertEquals("a", tableSasIpRange.getIpMin()); + assertNull(tableSasIpRange.getIpMax()); + + tableSasIpRange = TableSasIpRange.parse(""); + + assertEquals("", tableSasIpRange.getIpMin()); + assertNull(tableSasIpRange.getIpMax()); + } + + @Test + public void tableSasProtocol_parse() { + assertEquals(TableSasProtocol.HTTPS_ONLY, TableSasProtocol.parse("https")); + assertEquals(TableSasProtocol.HTTPS_HTTP, TableSasProtocol.parse("https,http")); + } + + @Test + public void create_tableSasSignatureValues_WithMinimumValues() { + OffsetDateTime expiryTime = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + TableSasPermission permissions = TableSasPermission.parse("r"); + + OffsetDateTime startTime = OffsetDateTime.of(2015, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + TableSasIpRange ipRange = TableSasIpRange.parse("a-b"); + TableSasProtocol protocol = TableSasProtocol.HTTPS_ONLY; + String startPartitionKey = "startPartitionKey"; + String startRowKey = "startRowKey"; + String endPartitionKey = "endPartitionKey"; + String endRowKey = "endRowKey"; + + TableSasSignatureValues sasSignatureValues = + new TableSasSignatureValues(expiryTime, permissions) + .setStartTime(startTime) + .setSasIpRange(ipRange) + .setProtocol(protocol) + .setStartPartitionKey(startPartitionKey) + .setStartRowKey(startRowKey) + .setEndPartitionKey(endPartitionKey) + .setEndRowKey(endRowKey); + + assertEquals(expiryTime, sasSignatureValues.getExpiryTime()); + assertEquals(permissions.toString(), sasSignatureValues.getPermissions()); + assertEquals(startTime, sasSignatureValues.getStartTime()); + assertEquals(ipRange, sasSignatureValues.getSasIpRange()); + assertEquals(protocol, sasSignatureValues.getProtocol()); + assertEquals(startPartitionKey, sasSignatureValues.getStartPartitionKey()); + assertEquals(startRowKey, sasSignatureValues.getStartRowKey()); + assertEquals(endPartitionKey, sasSignatureValues.getEndPartitionKey()); + assertEquals(endRowKey, sasSignatureValues.getEndRowKey()); + } + + @Test + public void create_tableSasSignatureValues_WithNullRequiredValue() { + OffsetDateTime expiryTime = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + TableSasPermission permissions = TableSasPermission.parse("r"); + + assertThrows(NullPointerException.class, + () -> new TableSasSignatureValues(null, permissions)); + assertThrows(NullPointerException.class, + () -> new TableSasSignatureValues(expiryTime, null)); + } + + @Test + public void tableSasPermission_toString() { + assertEquals("raup", new TableSasPermission() + .setReadPermission(true) + .setAddPermission(true) + .setUpdatePermission(true) + .setProcessPermission(true) + .toString()); + assertEquals("r", new TableSasPermission().setReadPermission(true).toString()); + assertEquals("a", new TableSasPermission().setAddPermission(true).toString()); + assertEquals("u", new TableSasPermission().setUpdatePermission(true).toString()); + assertEquals("p", new TableSasPermission().setProcessPermission(true).toString()); + } + + @Test + public void tableSasPermission_parse() { + TableSasPermission tableSasPermission = TableSasPermission.parse("raup"); + + assertTrue(tableSasPermission.hasReadPermission()); + assertTrue(tableSasPermission.hasAddPermission()); + assertTrue(tableSasPermission.hasUpdatePermission()); + assertTrue(tableSasPermission.hasProcessPermission()); + + tableSasPermission = TableSasPermission.parse("urpa"); + + assertTrue(tableSasPermission.hasReadPermission()); + assertTrue(tableSasPermission.hasAddPermission()); + assertTrue(tableSasPermission.hasUpdatePermission()); + assertTrue(tableSasPermission.hasProcessPermission()); + + assertTrue(TableSasPermission.parse("r").hasReadPermission()); + assertTrue(TableSasPermission.parse("a").hasAddPermission()); + assertTrue(TableSasPermission.parse("u").hasUpdatePermission()); + assertTrue(TableSasPermission.parse("p").hasProcessPermission()); + } + + @Test + public void tableSasPermission_parse_illegalString() { + assertThrows(IllegalArgumentException.class, () -> TableSasPermission.parse("rauq")); + } +} From 7578f7c254ca56aade4993f418aee23656933bf5 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Wed, 2 Jun 2021 16:48:09 -0500 Subject: [PATCH 10/25] Added builder tests for when multiple forms of authentication are set. --- .../azure/data/tables/TableClientBuilder.java | 14 ++++++++++++++ .../data/tables/TableServiceClientBuilder.java | 14 ++++++++++++++ .../tables/TableServiceClientBuilderTest.java | 16 ++++++++++++++++ .../data/tables/TablesClientBuilderTest.java | 15 +++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java index f449b8688eca3..ae9954f9aa3f0 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java @@ -98,7 +98,9 @@ public TableAsyncClient buildAsyncClient() { * * @return The updated {@link TableClientBuilder}. * + * @throws NullPointerException If {@code connectionString} is {@code null}. * @throws IllegalArgumentException If {@code connectionString} isn't a valid connection string. + * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableClientBuilder connectionString(String connectionString) { if (connectionString == null) { @@ -116,6 +118,18 @@ public TableClientBuilder connectionString(String connectionString) { this.endpoint(endpoint.getPrimaryUri()); + if (this.azureNamedKeyCredential != null || this.azureSasCredential != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this 'connectionString'. A credential has already been set for" + + " this builder to be used for authentication.")); + } + + if (this.sasToken != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this 'connectionString'. A SAS token has already been set for" + + " this builder to be used for authentication.")); + } + StorageAuthenticationSettings authSettings = storageConnectionString.getStorageAuthSettings(); if (authSettings.getType() == StorageAuthenticationSettings.Type.ACCOUNT_NAME_KEY) { diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java index ee6b6ec66dc2a..2ffededee416a 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java @@ -91,7 +91,9 @@ public TableServiceAsyncClient buildAsyncClient() { * * @return The updated {@link TableServiceClientBuilder}. * + * @throws NullPointerException If {@code connectionString} is {@code null}. * @throws IllegalArgumentException If {@code connectionString} isn't a valid connection string. + * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableServiceClientBuilder connectionString(String connectionString) { if (connectionString == null) { @@ -109,6 +111,18 @@ public TableServiceClientBuilder connectionString(String connectionString) { this.endpoint(endpoint.getPrimaryUri()); + if (this.azureNamedKeyCredential != null || this.azureSasCredential != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this 'connectionString'. A credential has already been set for" + + " this builder to be used for authentication.")); + } + + if (this.sasToken != null) { + throw logger.logExceptionAsError( + new IllegalStateException("Cannot set this 'connectionString'. A SAS token has already been set for" + + " this builder to be used for authentication.")); + } + StorageAuthenticationSettings authSettings = storageConnectionString.getStorageAuthSettings(); if (authSettings.getType() == StorageAuthenticationSettings.Type.ACCOUNT_NAME_KEY) { diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java index c41a9e9fbd855..ff77114c9fead 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java @@ -183,4 +183,20 @@ public void addPerCallPolicy() { assertTrue(perCallPolicyPosition < retryPolicyPosition); assertTrue(retryPolicyPosition < perRetryPolicyPosition); } + + @Test + public void multipleFormsOfAuthenticationPresent() { + TableServiceClientBuilder tableServiceClientBuilder = new TableServiceClientBuilder().sasToken("sasToken"); + + assertThrows(IllegalStateException.class, () -> tableServiceClientBuilder.connectionString(connectionString)); + assertThrows(IllegalStateException.class, + () -> tableServiceClientBuilder.credential(new AzureNamedKeyCredential("name", "key"))); + assertThrows(IllegalStateException.class, + () -> tableServiceClientBuilder.credential(new AzureSasCredential("sasToken"))); + + TableServiceClientBuilder tableServiceClientBuilder2 = + new TableServiceClientBuilder().connectionString(connectionString); + + assertThrows(IllegalStateException.class, () -> tableServiceClientBuilder2.sasToken("sasToken")); + } } diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesClientBuilderTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesClientBuilderTest.java index 9104f15fc5179..ee027c51e7f2f 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesClientBuilderTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesClientBuilderTest.java @@ -192,4 +192,19 @@ public void addPerCallPolicy() { assertTrue(perCallPolicyPosition < retryPolicyPosition); assertTrue(retryPolicyPosition < perRetryPolicyPosition); } + + @Test + public void multipleFormsOfAuthenticationPresent() { + TableClientBuilder tableClientBuilder = new TableClientBuilder().sasToken("sasToken"); + + assertThrows(IllegalStateException.class, () -> tableClientBuilder.connectionString(connectionString)); + assertThrows(IllegalStateException.class, + () -> tableClientBuilder.credential(new AzureNamedKeyCredential("name", "key"))); + assertThrows(IllegalStateException.class, + () -> tableClientBuilder.credential(new AzureSasCredential("sasToken"))); + + TableClientBuilder tableClientBuilder2 = new TableClientBuilder().connectionString(connectionString); + + assertThrows(IllegalStateException.class, () -> tableClientBuilder2.sasToken("sasToken")); + } } From a0dc7bcb46c69a21907c2d5124577b0f8c611ed8 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Wed, 2 Jun 2021 17:00:34 -0500 Subject: [PATCH 11/25] Updated builders to throw when no endpoint or form of authentication are provided. --- .../java/com/azure/data/tables/BuilderHelper.java | 11 ++++++++++- .../com/azure/data/tables/TableClientBuilder.java | 10 ++++++---- .../azure/data/tables/TableServiceAsyncClient.java | 2 +- .../azure/data/tables/TableServiceClientBuilder.java | 8 ++++++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java index dbf542de64eb2..c2583e8053cae 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java @@ -51,7 +51,11 @@ static HttpPipeline buildPipeline( // Closest to API goes first, closest to wire goes last. List policies = new ArrayList<>(); - if (endpoint.contains(COSMOS_ENDPOINT_SUFFIX)) { + if (endpoint == null) { + throw logger.logExceptionAsError( + new IllegalStateException("An 'endpoint' is required to create a client. Use a builder's 'endpoint()'" + + " or 'connectionString()' methods to set this value.")); + }else if (endpoint.contains(COSMOS_ENDPOINT_SUFFIX)) { policies.add(new CosmosPatchTransformPolicy()); } @@ -78,6 +82,7 @@ static HttpPipeline buildPipeline( policies.add(new AddDatePolicy()); HttpPipelinePolicy credentialPolicy; + if (azureNamedKeyCredential != null) { credentialPolicy = new TableAzureNamedKeyCredentialPolicy(azureNamedKeyCredential); } else if (azureSasCredential != null) { @@ -90,6 +95,10 @@ static HttpPipeline buildPipeline( if (credentialPolicy != null) { policies.add(credentialPolicy); + } else { + throw logger.logExceptionAsError( + new IllegalStateException("A form of authentication is required to create a client. Use a builder's " + + "'credential()', 'sasToken()' or 'connectionString()' methods to set a form of authentication.")); } // Add per retry additional policies. diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java index ae9954f9aa3f0..6d3b42752f552 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java @@ -65,8 +65,9 @@ public TableClientBuilder() { * * @return A {@link TableClient} created from the configurations in this builder. * - * @throws IllegalArgumentException If {@code tableName} is {@code null} or empty. - * @throws IllegalStateException If multiple credentials have been specified. + * @throws NullPointerException If {@code endpoint} or {@code tableName} are {@code null}. + * @throws IllegalArgumentException If {@code endpoint} is malformed or empty or if {@code tableName} is empty. + * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified. */ public TableClient buildClient() { return new TableClient(buildAsyncClient()); @@ -77,8 +78,9 @@ public TableClient buildClient() { * * @return A {@link TableAsyncClient} created from the configurations in this builder. * - * @throws IllegalArgumentException If {@code tableName} is {@code null} or empty. - * @throws IllegalStateException If multiple credentials have been specified. + * @throws NullPointerException If {@code endpoint} or {@code tableName} are {@code null}. + * @throws IllegalArgumentException If {@code endpoint} is malformed or empty or if {@code tableName} is empty. + * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified. */ public TableAsyncClient buildAsyncClient() { TableServiceVersion serviceVersion = version != null ? version : TableServiceVersion.getLatest(); diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java index b750d8d8d45e1..0e7b4796691ba 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java @@ -84,7 +84,7 @@ public final class TableServiceAsyncClient { this.accountName = uri.getHost().split("\\.", 2)[0]; logger.verbose("Table Service URI: {}", uri); - } catch (IllegalArgumentException ex) { + } catch (NullPointerException | IllegalArgumentException ex) { throw logger.logExceptionAsError(ex); } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java index 2ffededee416a..faec7a57b2377 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java @@ -61,7 +61,9 @@ public TableServiceClientBuilder() { * * @return A {@link TableServiceClient} created from the configurations in this builder. * - * @throws IllegalStateException If multiple credentials have been specified. + * @throws NullPointerException If {@code endpoint} is {@code null}. + * @throws IllegalArgumentException If {@code endpoint} is malformed or empty. + * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified. */ public TableServiceClient buildClient() { return new TableServiceClient(buildAsyncClient()); @@ -72,7 +74,9 @@ public TableServiceClient buildClient() { * * @return A {@link TableServiceAsyncClient} created from the configurations in this builder. * - * @throws IllegalStateException If multiple credentials have been specified. + * @throws NullPointerException If {@code endpoint} is {@code null}. + * @throws IllegalArgumentException If {@code endpoint} is malformed or empty. + * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified. */ public TableServiceAsyncClient buildAsyncClient() { TableServiceVersion serviceVersion = version != null ? version : TableServiceVersion.getLatest(); From 385656a78b00a61024734171c15b7e4912403389 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Wed, 2 Jun 2021 17:20:08 -0500 Subject: [PATCH 12/25] Fixed CheckStyle issues. --- .../com/azure/data/tables/BuilderHelper.java | 2 +- .../data/tables/{sas => }/SasModelsTest.java | 48 +++++++++++-------- 2 files changed, 29 insertions(+), 21 deletions(-) rename sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/{sas => }/SasModelsTest.java (90%) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java index c2583e8053cae..7512250a785af 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java @@ -55,7 +55,7 @@ static HttpPipeline buildPipeline( throw logger.logExceptionAsError( new IllegalStateException("An 'endpoint' is required to create a client. Use a builder's 'endpoint()'" + " or 'connectionString()' methods to set this value.")); - }else if (endpoint.contains(COSMOS_ENDPOINT_SUFFIX)) { + } else if (endpoint.contains(COSMOS_ENDPOINT_SUFFIX)) { policies.add(new CosmosPatchTransformPolicy()); } diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/sas/SasModelsTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java similarity index 90% rename from sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/sas/SasModelsTest.java rename to sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java index 518768c27e83d..7988c4e5ae37d 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/sas/SasModelsTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java @@ -1,7 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.data.tables.sas; - +package com.azure.data.tables; + +import com.azure.data.tables.sas.TableAccountSasPermission; +import com.azure.data.tables.sas.TableAccountSasResourceType; +import com.azure.data.tables.sas.TableAccountSasService; +import com.azure.data.tables.sas.TableAccountSasSignatureValues; +import com.azure.data.tables.sas.TableSasIpRange; +import com.azure.data.tables.sas.TableSasPermission; +import com.azure.data.tables.sas.TableSasProtocol; +import com.azure.data.tables.sas.TableSasSignatureValues; import org.junit.jupiter.api.Test; import java.time.OffsetDateTime; @@ -14,7 +22,7 @@ public class SasModelsTest { @Test - public void create_tableAccountSasSignatureValues_WithMinimumValues() { + public void createTableAccountSasSignatureValuesWithMinimumValues() { OffsetDateTime expiryTime = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); TableAccountSasPermission permissions = TableAccountSasPermission.parse("l"); TableAccountSasService services = TableAccountSasService.parse("t"); @@ -40,7 +48,7 @@ public void create_tableAccountSasSignatureValues_WithMinimumValues() { } @Test - public void create_tableAccountSasSignatureValues_WithNullRequiredValue() { + public void createTableAccountSasSignatureValuesWithNullRequiredValue() { OffsetDateTime expiryTime = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); TableAccountSasPermission permissions = TableAccountSasPermission.parse("l"); // List permission TableAccountSasService services = TableAccountSasService.parse("t"); // Tables service @@ -57,7 +65,7 @@ public void create_tableAccountSasSignatureValues_WithNullRequiredValue() { } @Test - public void tableAccountSasPermission_toString() { + public void tableAccountSasPermissionToString() { assertEquals("rwdxlacuptf", new TableAccountSasPermission() .setReadPermission(true) .setWritePermission(true) @@ -85,7 +93,7 @@ public void tableAccountSasPermission_toString() { } @Test - public void tableAccountSasPermission_parse() { + public void tableAccountSasPermissionParse() { TableAccountSasPermission tableAccountSasPermission = TableAccountSasPermission.parse("rwdxlacuptf"); assertTrue(tableAccountSasPermission.hasReadPermission()); @@ -128,7 +136,7 @@ public void tableAccountSasPermission_parse() { } @Test - public void tableAccountSasPermission_parse_illegalString() { + public void tableAccountSasPermissionParseIllegalString() { assertThrows(IllegalArgumentException.class, () -> TableAccountSasPermission.parse("rwaq")); } @@ -146,7 +154,7 @@ public void tableAccountSasResourceType_toString() { } @Test - public void tableAccountSasResourceType_parse() { + public void tableAccountSasResourceTypeParse() { TableAccountSasResourceType tableAccountSasResourceType = TableAccountSasResourceType.parse("sco"); assertTrue(tableAccountSasResourceType.isService()); @@ -159,12 +167,12 @@ public void tableAccountSasResourceType_parse() { } @Test - public void tableAccountSasResourceType_parse_illegalString() { + public void tableAccountSasResourceTypeParseIllegalString() { assertThrows(IllegalArgumentException.class, () -> TableAccountSasResourceType.parse("scq")); } @Test - public void tableAccountSasService_toString() { + public void tableAccountSasServiceToString() { assertEquals("bqtf", new TableAccountSasService() .setBlobAccess(true) .setQueueAccess(true) @@ -179,7 +187,7 @@ public void tableAccountSasService_toString() { } @Test - public void tableAccountSasService_parse() { + public void tableAccountSasServiceParse() { TableAccountSasService tableAccountSasService = TableAccountSasService.parse("bqtf"); assertTrue(tableAccountSasService.hasBlobAccess()); @@ -194,12 +202,12 @@ public void tableAccountSasService_parse() { } @Test - public void tableAccountSasService_parse_illegalString() { + public void tableAccountSasServiceParseIllegalString() { assertThrows(IllegalArgumentException.class, () -> TableAccountSasService.parse("bqta")); } @Test - public void tableSasIpRange_toString() { + public void tableSasIpRangeToString() { assertEquals("a-b", new TableSasIpRange() .setIpMin("a") .setIpMax("b") @@ -210,7 +218,7 @@ public void tableSasIpRange_toString() { } @Test - public void tableSasIpRange_parse() { + public void tableSasIpRangeParse() { TableSasIpRange tableSasIpRange = TableSasIpRange.parse("a-b"); assertEquals("a", tableSasIpRange.getIpMin()); @@ -228,13 +236,13 @@ public void tableSasIpRange_parse() { } @Test - public void tableSasProtocol_parse() { + public void tableSasProtocolParse() { assertEquals(TableSasProtocol.HTTPS_ONLY, TableSasProtocol.parse("https")); assertEquals(TableSasProtocol.HTTPS_HTTP, TableSasProtocol.parse("https,http")); } @Test - public void create_tableSasSignatureValues_WithMinimumValues() { + public void createTableSasSignatureValuesWithMinimumValues() { OffsetDateTime expiryTime = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); TableSasPermission permissions = TableSasPermission.parse("r"); @@ -268,7 +276,7 @@ public void create_tableSasSignatureValues_WithMinimumValues() { } @Test - public void create_tableSasSignatureValues_WithNullRequiredValue() { + public void createTableSasSignatureValuesWithNullRequiredValue() { OffsetDateTime expiryTime = OffsetDateTime.of(2017, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); TableSasPermission permissions = TableSasPermission.parse("r"); @@ -279,7 +287,7 @@ public void create_tableSasSignatureValues_WithNullRequiredValue() { } @Test - public void tableSasPermission_toString() { + public void tableSasPermissionToString() { assertEquals("raup", new TableSasPermission() .setReadPermission(true) .setAddPermission(true) @@ -293,7 +301,7 @@ public void tableSasPermission_toString() { } @Test - public void tableSasPermission_parse() { + public void tableSasPermissionParse() { TableSasPermission tableSasPermission = TableSasPermission.parse("raup"); assertTrue(tableSasPermission.hasReadPermission()); @@ -315,7 +323,7 @@ public void tableSasPermission_parse() { } @Test - public void tableSasPermission_parse_illegalString() { + public void tableSasPermissionParseIllegalString() { assertThrows(IllegalArgumentException.class, () -> TableSasPermission.parse("rauq")); } } From 62ec3ced0bdf6cd575b4d8d1717669ef51af39a9 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Wed, 2 Jun 2021 17:42:26 -0500 Subject: [PATCH 13/25] Fixed test name. --- .../src/test/java/com/azure/data/tables/SasModelsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java index 7988c4e5ae37d..0e2ce04aa3b2b 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java @@ -141,7 +141,7 @@ public void tableAccountSasPermissionParseIllegalString() { } @Test - public void tableAccountSasResourceType_toString() { + public void tableAccountSasResourceTypeToString() { assertEquals("sco", new TableAccountSasResourceType() .setService(true) .setContainer(true) From 475bd88e2a7ccd8a3e0320b03595d0b5e841f86c Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Wed, 2 Jun 2021 18:53:18 -0500 Subject: [PATCH 14/25] Removed unnecessary exports for implementation packages in module-info.java --- sdk/tables/azure-data-tables/src/main/java/module-info.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/module-info.java b/sdk/tables/azure-data-tables/src/main/java/module-info.java index 94f4c1eb8226b..a4df97c48c881 100644 --- a/sdk/tables/azure-data-tables/src/main/java/module-info.java +++ b/sdk/tables/azure-data-tables/src/main/java/module-info.java @@ -9,9 +9,6 @@ exports com.azure.data.tables.models; exports com.azure.data.tables.sas; - exports com.azure.data.tables.implementation to com.azure.core; - exports com.azure.data.tables.implementation.models to com.azure.core; - // exporting some packages specifically for Jackson opens com.azure.data.tables to com.fasterxml.jackson.databind, com.azure.core; opens com.azure.data.tables.implementation to com.fasterxml.jackson.databind, com.azure.core; From cd25daa11750c03f15802e43ee202ca6edf0cd1b Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 3 Jun 2021 19:25:03 -0500 Subject: [PATCH 15/25] Applied PR feedback: - Added extra clarity to when SAS models' toString() methods can return an empty String. - Removed unnecessary empty constructors in TableSasIpRange and TableSasPermission. - Changed builder parameter validation logic to the `buildClient()` and `buildAsyncClient()` methods. - Builders now also throw an IllegalStateException when calling `buildClient()` and `buildAsyncClient()` if multiple forms of authentication are provided, with the exception of 'sasToken' + 'connectionString'; or if 'endpoint' and/or 'sasToken' are set alongside a 'connectionString' and the endpoint and/or SAS token in the latter are different, respectively. - Removed "en-us" from all links in JavaDoc. - Updated CHANGELOG. --- sdk/tables/azure-data-tables/CHANGELOG.md | 3 +- .../com/azure/data/tables/BuilderHelper.java | 70 ++++++++++- .../azure/data/tables/TableAsyncClient.java | 3 +- .../TableAzureNamedKeyCredentialPolicy.java | 4 +- .../azure/data/tables/TableClientBuilder.java | 119 +++++++++--------- .../data/tables/TableServiceAsyncClient.java | 2 +- .../tables/TableServiceClientBuilder.java | 118 ++++++++--------- .../TableAccountSasGenerator.java | 12 +- .../implementation/TableSasGenerator.java | 12 +- .../tables/implementation/TableSasUtils.java | 16 +-- .../tables/implementation/TableUtils.java | 68 ++++++++++ .../tables/sas/TableAccountSasPermission.java | 4 +- .../sas/TableAccountSasResourceType.java | 5 +- .../tables/sas/TableAccountSasService.java | 4 +- .../data/tables/sas/TableSasIpRange.java | 10 +- .../data/tables/sas/TableSasPermission.java | 10 +- .../tables/sas/TableSasSignatureValues.java | 4 +- 17 files changed, 270 insertions(+), 194 deletions(-) diff --git a/sdk/tables/azure-data-tables/CHANGELOG.md b/sdk/tables/azure-data-tables/CHANGELOG.md index e3e9832f75752..a0ab2d5bfd58c 100644 --- a/sdk/tables/azure-data-tables/CHANGELOG.md +++ b/sdk/tables/azure-data-tables/CHANGELOG.md @@ -23,7 +23,8 @@ - `updateEntity(TableEntity entity, TableEntityUpdateMode updateMode, boolean ifUnchanged)` - `getEntity(String partitionKey, String rowKey, List select)` -- Using any of `credential(AzureNamedKeyCredential)`, `credential(AzureSasCredential)` and `sasToken(String)` in client builders now overrides configuration set by the remaining two methods. +- Client builders now throw an `IllegalStateException` when calling `buildClient()` or `buildAsyncClient()` if more than one of `credential(AzureNamedKeyCredential)`, `credential(AzureSasCredential)`, `sasToken(String)` and `connectionString(String)` were used, as providing more than one form of authentication is not allowed. +- Client builders now throw an `IllegalStateException` when calling `buildClient()` or `buildAsyncClient()` if no endpoint or form of authentication has been set. ## 12.0.0-beta.7 (2021-05-15) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java index 7512250a785af..b89effa451685 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java @@ -26,10 +26,18 @@ import com.azure.core.util.logging.ClientLogger; import com.azure.data.tables.implementation.CosmosPatchTransformPolicy; import com.azure.data.tables.implementation.NullHttpClient; +import com.azure.data.tables.implementation.StorageAuthenticationSettings; +import com.azure.data.tables.implementation.StorageConnectionString; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.azure.data.tables.implementation.TableUtils.sasTokenEquals; final class BuilderHelper { private static final Map PROPERTIES = @@ -81,6 +89,7 @@ static HttpPipeline buildPipeline( policies.add(retryPolicy); policies.add(new AddDatePolicy()); + HttpPipelinePolicy credentialPolicy; if (azureNamedKeyCredential != null) { @@ -89,18 +98,14 @@ static HttpPipeline buildPipeline( credentialPolicy = new AzureSasCredentialPolicy(azureSasCredential, false); } else if (sasToken != null) { credentialPolicy = new AzureSasCredentialPolicy(new AzureSasCredential(sasToken), false); - } else { - credentialPolicy = null; - } - - if (credentialPolicy != null) { - policies.add(credentialPolicy); } else { throw logger.logExceptionAsError( new IllegalStateException("A form of authentication is required to create a client. Use a builder's " + "'credential()', 'sasToken()' or 'connectionString()' methods to set a form of authentication.")); } + policies.add(credentialPolicy); + // Add per retry additional policies. policies.addAll(perRetryAdditionalPolicies); HttpPolicyProviders.addAfterRetryPolicies(policies); //should this be between 3/4? @@ -124,4 +129,57 @@ static HttpPipeline buildNullClientPipeline() { .httpClient(new NullHttpClient()) .build(); } + + static void validateCredentials(AzureNamedKeyCredential azureNamedKeyCredential, + AzureSasCredential azureSasCredential, String sasToken, String connectionString, + ClientLogger logger) { + List usedCredentials = + Stream.of(azureNamedKeyCredential, azureSasCredential, sasToken, connectionString) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + // Only allow two forms of authentication when 'connectionString' and 'sasToken' are provided. Validate that + // both contain the same SAS settings. + if (usedCredentials.size() == 2 && connectionString != null && sasToken != null) { + StorageConnectionString storageConnectionString = + StorageConnectionString.create(connectionString, logger); + StorageAuthenticationSettings authSettings = storageConnectionString.getStorageAuthSettings(); + + if (authSettings.getType() == StorageAuthenticationSettings.Type.SAS_TOKEN) { + if (sasTokenEquals(sasToken, authSettings.getSasToken())) { + return; + } else { + throw logger.logExceptionAsError(new IllegalStateException("'connectionString' contains a SAS token" + + " with different settings than 'sasToken'.")); + } + } + + // If the 'connectionString' auth type is not SAS_TOKEN and a 'sasToken' was provided, then multiplte + // incompatible forms of authentication were specified in the client builder. + } + + if (usedCredentials.size() > 1) { + StringJoiner usedCredentialsStringBuilder = new StringJoiner(", "); + + if (azureNamedKeyCredential != null) { + usedCredentialsStringBuilder.add("azureNamedKeyCredential"); + } + + if (azureSasCredential != null) { + usedCredentialsStringBuilder.add("azureSasCredential"); + } + + if (sasToken != null) { + usedCredentialsStringBuilder.add("sasToken"); + } + + if (connectionString != null) { + usedCredentialsStringBuilder.add("connectionString"); + } + + throw logger.logExceptionAsError(new IllegalStateException( + "Only one form of authentication should be used. The authentication forms present are: " + + usedCredentialsStringBuilder + ".")); + } + } } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java index cc4e4a60012da..39fb8c2c53102 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java @@ -238,8 +238,7 @@ public String generateSas(TableSasSignatureValues tableSasSignatureValues, Conte + " is not authenticated with an AzureNamedKeyCredential.")); } - return new TableSasGenerator(tableSasSignatureValues, getTableName(), azureNamedKeyCredential, context) - .getSas(); + return new TableSasGenerator(tableSasSignatureValues, getTableName(), azureNamedKeyCredential).getSas(); } /** diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAzureNamedKeyCredentialPolicy.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAzureNamedKeyCredentialPolicy.java index 1c2fe511ebee3..0701a99db7e05 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAzureNamedKeyCredentialPolicy.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAzureNamedKeyCredentialPolicy.java @@ -17,7 +17,7 @@ import java.util.Locale; import java.util.Map; -import static com.azure.data.tables.implementation.TableSasUtils.computeHMac256; +import static com.azure.data.tables.implementation.TableSasUtils.computeHmac256; import static com.azure.data.tables.implementation.TableUtils.parseQueryStringSplitValues; /** @@ -61,7 +61,7 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN * @return The auth header */ String generateAuthorizationHeader(URL requestUrl, Map headers) { - String signature = computeHMac256(credential.getAzureNamedKey().getKey(), buildStringToSign(requestUrl, + String signature = computeHmac256(credential.getAzureNamedKey().getKey(), buildStringToSign(requestUrl, headers)); return String.format(AUTHORIZATION_HEADER_FORMAT, credential.getAzureNamedKey().getName(), signature); } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java index 6d3b42752f552..7aa2a6f5d53bd 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java @@ -27,6 +27,8 @@ import java.util.ArrayList; import java.util.List; +import static com.azure.data.tables.BuilderHelper.validateCredentials; + /** * This class provides a fluent builder API to help aid the configuration and instantiation of {@link TableClient} and * {@link TableAsyncClient} objects. Call {@link #buildClient()} or {@link #buildAsyncClient()}, respectively, to @@ -43,6 +45,7 @@ public final class TableClientBuilder { private String tableName; private Configuration configuration; private HttpClient httpClient; + private String connectionString; private String endpoint; private HttpLogOptions httpLogOptions; private ClientOptions clientOptions; @@ -80,11 +83,60 @@ public TableClient buildClient() { * * @throws NullPointerException If {@code endpoint} or {@code tableName} are {@code null}. * @throws IllegalArgumentException If {@code endpoint} is malformed or empty or if {@code tableName} is empty. - * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified. + * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified or if + * multiple forms of authentication are provided, with the exception of {@code sasToken} + + * {@code connectionString}. Also thrown if {@code endpoint} and/or {@code sasToken} are set alongside a + * {@code connectionString} and the endpoint and/or SAS token in the latter are different, respectively. */ public TableAsyncClient buildAsyncClient() { TableServiceVersion serviceVersion = version != null ? version : TableServiceVersion.getLatest(); + validateCredentials(azureNamedKeyCredential, azureSasCredential, sasToken, connectionString, logger); + + // If 'connectionString' was provided, extract the endpoint and sasToken. + if (connectionString != null) { + StorageConnectionString storageConnectionString = StorageConnectionString.create(connectionString, logger); + StorageEndpoint storageConnectionStringTableEndpoint = storageConnectionString.getTableEndpoint(); + + if (storageConnectionStringTableEndpoint == null + || storageConnectionStringTableEndpoint.getPrimaryUri() == null) { + + throw logger.logExceptionAsError(new IllegalArgumentException( + "'connectionString' is missing the required settings to derive a Tables endpoint.")); + } + + String connectionStringEndpoint = storageConnectionStringTableEndpoint.getPrimaryUri(); + + // If no 'endpoint' was provided, use the one in the 'connectionString'. Else, verify they are the same. + if (endpoint == null) { + endpoint = connectionStringEndpoint; + } else { + if (endpoint.endsWith("/")) { + endpoint = endpoint.substring(0, endpoint.length() - 1); + } + + if (connectionStringEndpoint.endsWith("/")) { + connectionStringEndpoint = + connectionStringEndpoint.substring(0, connectionStringEndpoint.length() - 1); + } + + if (!endpoint.equals(connectionStringEndpoint)) { + throw logger.logExceptionAsError(new IllegalStateException( + "'endpoint' points to a different tables endpoint than 'connectionString'.")); + } + } + + StorageAuthenticationSettings authSettings = storageConnectionString.getStorageAuthSettings(); + + if (authSettings.getType() == StorageAuthenticationSettings.Type.ACCOUNT_NAME_KEY) { + azureNamedKeyCredential = (azureNamedKeyCredential != null) ? azureNamedKeyCredential + : new AzureNamedKeyCredential(authSettings.getAccount().getName(), + authSettings.getAccount().getAccessKey()); + } else if (authSettings.getType() == StorageAuthenticationSettings.Type.SAS_TOKEN) { + sasToken = (sasToken != null) ? sasToken : authSettings.getSasToken(); + } + } + HttpPipeline pipeline = (httpPipeline != null) ? httpPipeline : BuilderHelper.buildPipeline( azureNamedKeyCredential, azureSasCredential, sasToken, endpoint, retryPolicy, httpLogOptions, clientOptions, httpClient, perCallPolicies, perRetryPolicies, configuration, logger); @@ -102,44 +154,15 @@ public TableAsyncClient buildAsyncClient() { * * @throws NullPointerException If {@code connectionString} is {@code null}. * @throws IllegalArgumentException If {@code connectionString} isn't a valid connection string. - * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableClientBuilder connectionString(String connectionString) { if (connectionString == null) { throw logger.logExceptionAsError(new NullPointerException("'connectionString' cannot be null.")); } - StorageConnectionString storageConnectionString = StorageConnectionString.create(connectionString, logger); - StorageEndpoint endpoint = storageConnectionString.getTableEndpoint(); - - if (endpoint == null || endpoint.getPrimaryUri() == null) { - throw logger.logExceptionAsError( - new IllegalArgumentException( - "'connectionString' missing required settings to derive tables service endpoint.")); - } - - this.endpoint(endpoint.getPrimaryUri()); - - if (this.azureNamedKeyCredential != null || this.azureSasCredential != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this 'connectionString'. A credential has already been set for" - + " this builder to be used for authentication.")); - } - - if (this.sasToken != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this 'connectionString'. A SAS token has already been set for" - + " this builder to be used for authentication.")); - } + StorageConnectionString.create(connectionString, logger); - StorageAuthenticationSettings authSettings = storageConnectionString.getStorageAuthSettings(); - - if (authSettings.getType() == StorageAuthenticationSettings.Type.ACCOUNT_NAME_KEY) { - this.credential(new AzureNamedKeyCredential(authSettings.getAccount().getName(), - authSettings.getAccount().getAccessKey())); - } else if (authSettings.getType() == StorageAuthenticationSettings.Type.SAS_TOKEN) { - this.sasToken(authSettings.getSasToken()); - } + this.connectionString = connectionString; return this; } @@ -151,6 +174,7 @@ public TableClientBuilder connectionString(String connectionString) { * * @return The updated {@link TableClientBuilder}. * + * @throws NullPointerException If {@code endpoint} is {@code null}. * @throws IllegalArgumentException If {@code endpoint} isn't a valid URL. */ public TableClientBuilder endpoint(String endpoint) { @@ -209,7 +233,6 @@ public TableClientBuilder configuration(Configuration configuration) { * * @throws NullPointerException If {@code sasToken} is {@code null}. * @throws IllegalArgumentException If {@code sasToken} is empty. - * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableClientBuilder sasToken(String sasToken) { if (sasToken == null) { @@ -220,12 +243,6 @@ public TableClientBuilder sasToken(String sasToken) { throw logger.logExceptionAsError(new IllegalArgumentException("'sasToken' cannot be empty.")); } - if (this.azureNamedKeyCredential != null || this.azureSasCredential != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this 'sasToken'. A credential has already been set for this" - + " builder to be used for authentication.")); - } - this.sasToken = sasToken; return this; @@ -240,24 +257,12 @@ public TableClientBuilder sasToken(String sasToken) { * @return The updated {@link TableClientBuilder}. * * @throws NullPointerException If {@code credential} is {@code null}. - * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableClientBuilder credential(AzureSasCredential credential) { if (credential == null) { throw logger.logExceptionAsError(new NullPointerException("'credential' cannot be null.")); } - if (this.azureNamedKeyCredential != null) { - throw logger.logExceptionAsError( - new IllegalStateException("A credential of a different type has already been set for this builder.")); - } - - if (this.sasToken != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this credential. A SAS token has already been set for this" - + " builder to be used for authentication.")); - } - this.azureSasCredential = credential; return this; @@ -272,24 +277,12 @@ public TableClientBuilder credential(AzureSasCredential credential) { * @return The updated {@link TableClientBuilder}. * * @throws NullPointerException If {@code credential} is {@code null}. - * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableClientBuilder credential(AzureNamedKeyCredential credential) { if (credential == null) { throw logger.logExceptionAsError(new NullPointerException("'credential' cannot be null.")); } - if (this.azureSasCredential != null) { - throw logger.logExceptionAsError( - new IllegalStateException("A credential of a different type has already been set for this builder.")); - } - - if (this.sasToken != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this credential. A SAS token has already been set for this" - + " builder to be used for authentication.")); - } - this.azureNamedKeyCredential = credential; return this; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java index 0e7b4796691ba..7bfae300b6917 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java @@ -192,7 +192,7 @@ public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasS + " is not authenticated with an AzureNamedKeyCredential.")); } - return new TableAccountSasGenerator(tableAccountSasSignatureValues, azureNamedKeyCredential, context).getSas(); + return new TableAccountSasGenerator(tableAccountSasSignatureValues, azureNamedKeyCredential).getSas(); } /** diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java index faec7a57b2377..b2baae5e12d3e 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java @@ -26,6 +26,8 @@ import java.util.ArrayList; import java.util.List; +import static com.azure.data.tables.BuilderHelper.validateCredentials; + /** * This class provides a fluent builder API to help aid the configuration and instantiation of * {@link TableServiceClient} and {@link TableServiceAsyncClient} objects. Call {@link #buildClient()} or @@ -38,6 +40,7 @@ public final class TableServiceClientBuilder { private final List perCallPolicies = new ArrayList<>(); private final List perRetryPolicies = new ArrayList<>(); private Configuration configuration; + private String connectionString; private String endpoint; private HttpClient httpClient; private HttpLogOptions httpLogOptions; @@ -76,11 +79,60 @@ public TableServiceClient buildClient() { * * @throws NullPointerException If {@code endpoint} is {@code null}. * @throws IllegalArgumentException If {@code endpoint} is malformed or empty. - * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified. + * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified or if + * multiple forms of authentication are provided, with the exception of {@code sasToken} + + * {@code connectionString}. Also thrown if {@code endpoint} and/or {@code sasToken} are set alongside a + * {@code connectionString} and the endpoint and/or SAS token in the latter are different, respectively. */ public TableServiceAsyncClient buildAsyncClient() { TableServiceVersion serviceVersion = version != null ? version : TableServiceVersion.getLatest(); + validateCredentials(azureNamedKeyCredential, azureSasCredential, sasToken, connectionString, logger); + + // If 'connectionString' was provided, extract the endpoint and sasToken. + if (connectionString != null) { + StorageConnectionString storageConnectionString = StorageConnectionString.create(connectionString, logger); + StorageEndpoint storageConnectionStringTableEndpoint = storageConnectionString.getTableEndpoint(); + + if (storageConnectionStringTableEndpoint == null + || storageConnectionStringTableEndpoint.getPrimaryUri() == null) { + + throw logger.logExceptionAsError(new IllegalArgumentException( + "'connectionString' is missing the required settings to derive a Tables endpoint.")); + } + + String connectionStringEndpoint = storageConnectionStringTableEndpoint.getPrimaryUri(); + + // If no 'endpoint' was provided, use the one in the 'connectionString'. Else, verify they are the same. + if (endpoint == null) { + endpoint = connectionStringEndpoint; + } else { + if (endpoint.endsWith("/")) { + endpoint = endpoint.substring(0, endpoint.length() - 1); + } + + if (connectionStringEndpoint.endsWith("/")) { + connectionStringEndpoint = + connectionStringEndpoint.substring(0, connectionStringEndpoint.length() - 1); + } + + if (!endpoint.equals(connectionStringEndpoint)) { + throw logger.logExceptionAsError(new IllegalStateException( + "'endpoint' points to a different tables endpoint than 'connectionString'.")); + } + } + + StorageAuthenticationSettings authSettings = storageConnectionString.getStorageAuthSettings(); + + if (authSettings.getType() == StorageAuthenticationSettings.Type.ACCOUNT_NAME_KEY) { + azureNamedKeyCredential = (azureNamedKeyCredential != null) ? azureNamedKeyCredential + : new AzureNamedKeyCredential(authSettings.getAccount().getName(), + authSettings.getAccount().getAccessKey()); + } else if (authSettings.getType() == StorageAuthenticationSettings.Type.SAS_TOKEN) { + sasToken = (sasToken != null) ? sasToken : authSettings.getSasToken(); + } + } + HttpPipeline pipeline = (httpPipeline != null) ? httpPipeline : BuilderHelper.buildPipeline( azureNamedKeyCredential, azureSasCredential, sasToken, endpoint, retryPolicy, httpLogOptions, clientOptions, httpClient, perCallPolicies, perRetryPolicies, configuration, logger); @@ -97,44 +149,15 @@ public TableServiceAsyncClient buildAsyncClient() { * * @throws NullPointerException If {@code connectionString} is {@code null}. * @throws IllegalArgumentException If {@code connectionString} isn't a valid connection string. - * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableServiceClientBuilder connectionString(String connectionString) { if (connectionString == null) { throw logger.logExceptionAsError(new NullPointerException("'connectionString' cannot be null.")); } - StorageConnectionString storageConnectionString = StorageConnectionString.create(connectionString, logger); - StorageEndpoint endpoint = storageConnectionString.getTableEndpoint(); - - if (endpoint == null || endpoint.getPrimaryUri() == null) { - throw logger.logExceptionAsError( - new IllegalArgumentException( - "'connectionString' missing required settings to derive tables service endpoint.")); - } - - this.endpoint(endpoint.getPrimaryUri()); - - if (this.azureNamedKeyCredential != null || this.azureSasCredential != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this 'connectionString'. A credential has already been set for" - + " this builder to be used for authentication.")); - } - - if (this.sasToken != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this 'connectionString'. A SAS token has already been set for" - + " this builder to be used for authentication.")); - } + StorageConnectionString.create(connectionString, logger); - StorageAuthenticationSettings authSettings = storageConnectionString.getStorageAuthSettings(); - - if (authSettings.getType() == StorageAuthenticationSettings.Type.ACCOUNT_NAME_KEY) { - this.credential(new AzureNamedKeyCredential(authSettings.getAccount().getName(), - authSettings.getAccount().getAccessKey())); - } else if (authSettings.getType() == StorageAuthenticationSettings.Type.SAS_TOKEN) { - this.sasToken(authSettings.getSasToken()); - } + this.connectionString = connectionString; return this; } @@ -204,7 +227,6 @@ public TableServiceClientBuilder configuration(Configuration configuration) { * * @throws NullPointerException If {@code sasToken} is {@code null}. * @throws IllegalArgumentException If {@code sasToken} is empty. - * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableServiceClientBuilder sasToken(String sasToken) { if (sasToken == null) { @@ -215,12 +237,6 @@ public TableServiceClientBuilder sasToken(String sasToken) { throw logger.logExceptionAsError(new IllegalArgumentException("'sasToken' cannot be empty.")); } - if (this.azureNamedKeyCredential != null || this.azureSasCredential != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this 'sasToken'. A credential has already been set for this" - + " builder to be used for authentication.")); - } - this.sasToken = sasToken; return this; @@ -235,24 +251,12 @@ public TableServiceClientBuilder sasToken(String sasToken) { * @return The updated {@link TableServiceClientBuilder}. * * @throws NullPointerException If {@code credential} is {@code null}. - * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableServiceClientBuilder credential(AzureSasCredential credential) { if (credential == null) { throw logger.logExceptionAsError(new NullPointerException("'credential' cannot be null.")); } - if (this.azureNamedKeyCredential != null) { - throw logger.logExceptionAsError( - new IllegalStateException("A credential of a different type has already been set for this builder.")); - } - - if (this.sasToken != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this credential. A SAS token has already been set for this" - + " builder to be used for authentication.")); - } - this.azureSasCredential = credential; return this; @@ -267,24 +271,12 @@ public TableServiceClientBuilder credential(AzureSasCredential credential) { * @return The updated {@link TableServiceClientBuilder}. * * @throws NullPointerException If {@code credential} is {@code null}. - * @throws IllegalStateException If another form of authentication has already been set for this builder. */ public TableServiceClientBuilder credential(AzureNamedKeyCredential credential) { if (credential == null) { throw logger.logExceptionAsError(new NullPointerException("'credential' cannot be null.")); } - if (this.azureSasCredential != null) { - throw logger.logExceptionAsError( - new IllegalStateException("A credential of a different type has already been set for this builder.")); - } - - if (this.sasToken != null) { - throw logger.logExceptionAsError( - new IllegalStateException("Cannot set this credential. A SAS token has already been set for this" - + " builder to be used for authentication.")); - } - this.azureNamedKeyCredential = credential; return this; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java index fd5ab0e8940f1..a957435f5bf9c 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java @@ -3,7 +3,6 @@ package com.azure.data.tables.implementation; import com.azure.core.credential.AzureNamedKeyCredential; -import com.azure.core.util.Context; import com.azure.core.util.CoreUtils; import com.azure.core.util.logging.ClientLogger; import com.azure.data.tables.sas.TableAccountSasPermission; @@ -14,9 +13,8 @@ import java.time.OffsetDateTime; import java.util.Objects; -import static com.azure.data.tables.implementation.TableSasUtils.computeHMac256; +import static com.azure.data.tables.implementation.TableSasUtils.computeHmac256; import static com.azure.data.tables.implementation.TableSasUtils.formatQueryParameterDate; -import static com.azure.data.tables.implementation.TableSasUtils.logStringToSign; import static com.azure.data.tables.implementation.TableSasUtils.tryAppendQueryParameter; /** @@ -40,11 +38,9 @@ public class TableAccountSasGenerator { * * @param sasValues The {@link TableAccountSasSignatureValues account signature values}. * @param azureNamedKeyCredential An {@link AzureNamedKeyCredential} whose key will be used to sign the SAS. - * @param context Additional context that is passed through the code when generating a SAS. */ public TableAccountSasGenerator(TableAccountSasSignatureValues sasValues, - AzureNamedKeyCredential azureNamedKeyCredential, - Context context) { + AzureNamedKeyCredential azureNamedKeyCredential) { Objects.requireNonNull(sasValues, "'sasValues' cannot be null."); Objects.requireNonNull(azureNamedKeyCredential, "'azureNamedKeyCredential' cannot be null."); Objects.requireNonNull(sasValues.getServices(), "'services' in 'sasValues' cannot be null."); @@ -67,10 +63,8 @@ public TableAccountSasGenerator(TableAccountSasSignatureValues sasValues, String stringToSign = stringToSign(azureNamedKeyCredential); - logStringToSign(logger, stringToSign, context); - // Signature is generated on the un-url-encoded values. - String signature = computeHMac256(azureNamedKeyCredential.getAzureNamedKey().getKey(), stringToSign); + String signature = computeHmac256(azureNamedKeyCredential.getAzureNamedKey().getKey(), stringToSign); this.sas = encode(signature); } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java index 74b73797213b4..2156305333483 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java @@ -14,9 +14,8 @@ import java.time.OffsetDateTime; import java.util.Objects; -import static com.azure.data.tables.implementation.TableSasUtils.computeHMac256; +import static com.azure.data.tables.implementation.TableSasUtils.computeHmac256; import static com.azure.data.tables.implementation.TableSasUtils.formatQueryParameterDate; -import static com.azure.data.tables.implementation.TableSasUtils.logStringToSign; import static com.azure.data.tables.implementation.TableSasUtils.tryAppendQueryParameter; /** @@ -45,11 +44,9 @@ public class TableSasGenerator { * @param sasValues The {@link TableSasSignatureValues} to generate the SAS token with. * @param tableName The table name. * @param azureNamedKeyCredential An {@link AzureNamedKeyCredential} whose key will be used to sign the SAS. - * @param context Additional context that is passed through the code when generating a SAS. */ public TableSasGenerator(TableSasSignatureValues sasValues, String tableName, - AzureNamedKeyCredential azureNamedKeyCredential, - Context context) { + AzureNamedKeyCredential azureNamedKeyCredential) { Objects.requireNonNull(sasValues, "'sasValues' cannot be null."); Objects.requireNonNull(azureNamedKeyCredential, "'azureNamedKeyCredential' cannot be null."); @@ -71,10 +68,7 @@ public TableSasGenerator(TableSasSignatureValues sasValues, String tableName, // Signature is generated on the un-url-encoded values. String canonicalName = getCanonicalName(azureNamedKeyCredential.getAzureNamedKey().getName()); String stringToSign = stringToSign(canonicalName); - - logStringToSign(logger, stringToSign, context); - - String signature = computeHMac256(azureNamedKeyCredential.getAzureNamedKey().getKey(), stringToSign); + String signature = computeHmac256(azureNamedKeyCredential.getAzureNamedKey().getKey(), stringToSign); this.sas = encode(signature); } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java index 9c38fb6859c39..b88b61493ea63 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java @@ -55,20 +55,6 @@ public static String formatQueryParameterDate(OffsetDateTime dateTime) { } } - /** - * Logs the string to sign if a valid context is provided. - * - * @param logger A {@link ClientLogger} to log the signed string to. - * @param stringToSign The string to sign to log. - * @param context Additional context to determine if the string to sign should be logged. - */ - public static void logStringToSign(ClientLogger logger, String stringToSign, Context context) { - if (context != null && Boolean.TRUE.equals(context.getData(StorageConstants.STORAGE_LOG_STRING_TO_SIGN).orElse(false))) { - logger.info(STRING_TO_SIGN_LOG_INFO_MESSAGE, stringToSign, System.lineSeparator()); - logger.warning(STRING_TO_SIGN_LOG_WARNING_MESSAGE, StorageConstants.STORAGE_LOG_STRING_TO_SIGN); - } - } - /** * Extracts the {@link AzureNamedKeyCredential} from a {@link HttpPipeline} * @@ -99,7 +85,7 @@ public static AzureNamedKeyCredential extractNamedKeyCredential(HttpPipeline pip * @throws RuntimeException If the HMAC-SHA256 algorithm isn't support, if the key isn't a valid Base64 encoded * string, or the UTF-8 charset isn't supported. */ - public static String computeHMac256(final String base64Key, final String stringToSign) { + public static String computeHmac256(final String base64Key, final String stringToSign) { try { byte[] key = Base64.getDecoder().decode(base64Key); Mac hmacSHA256 = Mac.getInstance("HmacSHA256"); diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java index 682a8e64c73af..48d5e1b6911ef 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java @@ -19,6 +19,7 @@ import reactor.core.publisher.Mono; import java.io.UnsupportedEncodingException; +import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; import java.time.Duration; @@ -26,7 +27,10 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; @@ -444,4 +448,68 @@ public static Map parseQueryString(String queryParams) { return retVals; } + + public static boolean sasTokenEquals(String sasToken1, String sasToken2) { + List keysToCompareUnsorted = new ArrayList<>(); + keysToCompareUnsorted.add("ss"); // Services + keysToCompareUnsorted.add("srt"); // Resource types + keysToCompareUnsorted.add("sp"); // Permissions + + Map queryParams1 = parseQueryString(sasToken1); + Map queryParams2 = parseQueryString(sasToken2); + + if (queryParams1.size() != queryParams2.size()) { + // More query parameters on one token than the other. + return false; + } + + for (String key : queryParams1.keySet()) { + if (!queryParams2.containsKey(key)) { + // Different query parameters. + return false; + } + + String[] param1 = queryParams1.get(key); + String[] param2 = queryParams2.get(key); + + if (keysToCompareUnsorted.contains(key)) { + for (int i = 0; i < param1.length; i++) { + param1[i] = orderString(param1[i]); + } + + for (int i = 0; i < param2.length; i++) { + param2[i] = orderString(param2[i]); + } + + } + + if (!sasArrayEquals(param1, param2)) { + return false; + } + } + + // SAS tokens are the same. + return true; + } + + private static String orderString(String str) { + char[] charArray = str.toCharArray(); + + Arrays.sort(charArray); + + return new String(charArray); + } + + private static boolean sasArrayEquals(String[] array1, String[] array2) { + if (array1.length > 1) { + Arrays.sort(array1); + } + + if (array2.length > 1) { + Arrays.sort(array2); + } + + // Different values in these query parameters. + return Arrays.equals(array1, array2); + } } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java index 0297190352492..5569ca24fd7c2 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasPermission.java @@ -346,14 +346,14 @@ public TableAccountSasPermission setFilterTagsPermission(boolean filterTagsPermi /** * Converts the given permissions to a {@link String}. Using this method will guarantee the permissions are in an - * order accepted by the service. + * order accepted by the service. If all permissions are set to false, an empty string is returned from this method. * * @return A {@link String} which represents the {@link TableAccountSasPermission}. */ @Override public String toString() { // The order of the characters should be as specified here to ensure correctness: - // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas + // https://docs.microsoft.com/rest/api/storageservices/constructing-an-account-sas final StringBuilder builder = new StringBuilder(); if (this.readPermission) { diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java index 05a565a140f14..a0d3d6bc2082c 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasResourceType.java @@ -130,14 +130,15 @@ public TableAccountSasResourceType setObject(boolean object) { /** * Converts the given resource types to a {@link String}. Using this method will guarantee the resource types are in - * an order accepted by the service. + * an order accepted by the service. If all resource types are set to false, an empty string is returned from this + * method. * * @return A {@code String} which represents the {@link TableAccountSasResourceType account resource types}. */ @Override public String toString() { // The order of the characters should be as specified here to ensure correctness: - // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas + // https://docs.microsoft.com/rest/api/storageservices/constructing-an-account-sas StringBuilder builder = new StringBuilder(); if (this.service) { diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java index 3cf90b4e3d958..e6af4c1854b52 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableAccountSasService.java @@ -141,14 +141,14 @@ public TableAccountSasService setTableAccess(boolean table) { /** * Converts the given services to a {@link String}. Using this method will guarantee the services are in an order - * accepted by the service. + * accepted by the service. If all services are set to false, an empty string is returned from this method. * * @return A {@link String} which represents the {@link TableAccountSasService account services}. */ @Override public String toString() { // The order of the characters should be as specified here to ensure correctness: - // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas + // https://docs.microsoft.com/rest/api/storageservices/constructing-an-account-sas StringBuilder value = new StringBuilder(); if (this.blob) { diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java index ba0d838ded794..2c11f2a7fc70e 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java @@ -10,12 +10,6 @@ public final class TableSasIpRange { private String ipMin; private String ipMax; - /** - * Constructs an {@link TableSasIpRange} object. - */ - public TableSasIpRange() { - } - /** * Creates a {@link TableSasIpRange} from the specified string. * @@ -73,7 +67,9 @@ public TableSasIpRange setIpMax(String ipMax) { } /** - * Output the single IP address or range of IP addresses formatted as a {@link String}. + * Output the single IP address or range of IP addresses formatted as a {@link String}. If {@code minIpRange} is set + * to {@code null}, an empty string is returned from this method. Otherwise, if {@code maxIpRange} is set + * to {@code null}, then this method returns the value of {@code minIpRange}. * * @return The single IP address or range of IP addresses formatted as a {@link String}. */ diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java index c1d714757b88c..e5509441d9ace 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java @@ -29,12 +29,6 @@ public final class TableSasPermission { private boolean updatePermission; private boolean processPermission; - /** - * Initializes a {@link TableSasPermission} object with all fields set to {@code false}. - */ - public TableSasPermission() { - } - /** * Creates a {@link TableSasPermission} from the specified permissions string. This method will throw an * {@link IllegalArgumentException} if it encounters a character that does not correspond to a valid permission. @@ -179,14 +173,14 @@ public TableSasPermission setProcessPermission(boolean hasProcessPermission) { /** * Converts the given permissions to a {@link String}. Using this method will guarantee the permissions are in an - * order accepted by the service. + * order accepted by the service. If all permissions are set to false, an empty string is returned from this method. * * @return A {@link String} which represents the {@link TableSasPermission}. */ @Override public String toString() { // The order of the characters should be as specified here to ensure correctness: - // https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas + // https://docs.microsoft.com/rest/api/storageservices/constructing-a-service-sas final StringBuilder builder = new StringBuilder(); diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java index 39ecc13ecd277..0ea5851672a45 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java @@ -188,7 +188,7 @@ public TableSasSignatureValues setSasIpRange(TableSasIpRange sasIpRange) { /** * @return The name of the access policy on the table this SAS references if any. Please see - * here + * here * for more information. */ public String getIdentifier() { @@ -197,7 +197,7 @@ public String getIdentifier() { /** * Sets the name of the access policy on the table this SAS references if any. Please see - * here + * here * for more information. * * @param identifier Name of the access policy From 0d9820d8fbaef2fd48f58ec73860dc810cf3700c Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 3 Jun 2021 19:31:08 -0500 Subject: [PATCH 16/25] Added tests and renamed test classes to match clients and builders. --- ...entTest.java => TableAsyncClientTest.java} | 2 +- ...rTest.java => TableClientBuilderTest.java} | 90 +++++++++++++++++-- .../tables/TableServiceAsyncClientTest.java | 2 +- .../tables/TableServiceClientBuilderTest.java | 79 ++++++++++++++-- ...bleAsyncClientTest.createEntityAsync.json} | 0 ...ClientTest.createEntitySubclassAsync.json} | 0 ...EntityWithAllSupportedDataTypesAsync.json} | 0 ...ntTest.createEntityWithResponseAsync.json} | 0 ...ableAsyncClientTest.createTableAsync.json} | 0 ...entTest.createTableWithResponseAsync.json} | 0 ...bleAsyncClientTest.deleteEntityAsync.json} | 0 ...ntTest.deleteEntityWithResponseAsync.json} | 0 ...leteEntityWithResponseMatchETagAsync.json} | 0 ...entTest.deleteNonExistingEntityAsync.json} | 0 ...teNonExistingEntityWithResponseAsync.json} | 0 ...ientTest.deleteNonExistingTableAsync.json} | 0 ...eteNonExistingTableWithResponseAsync.json} | 0 ...ableAsyncClientTest.deleteTableAsync.json} | 0 ...entTest.deleteTableWithResponseAsync.json} | 0 ...lientTest.getEntityWithResponseAsync.json} | 0 ...t.getEntityWithResponseSubclassAsync.json} | 0 ...getEntityWithResponseWithSelectAsync.json} | 0 ...bleAsyncClientTest.listEntitiesAsync.json} | 0 ...ClientTest.listEntitiesSubclassAsync.json} | 0 ...ientTest.listEntitiesWithFilterAsync.json} | 0 ...ientTest.listEntitiesWithSelectAsync.json} | 0 ...cClientTest.listEntitiesWithTopAsync.json} | 0 ...yncClientTest.submitTransactionAsync.json} | 0 ...est.submitTransactionAsyncAllActions.json} | 0 ...ctionAsyncWithDifferentPartitionKeys.json} | 0 ...mitTransactionAsyncWithFailingAction.json} | 0 ...ubmitTransactionAsyncWithSameRowKeys.json} | 0 ...t.updateEntityWithResponseMergeAsync.json} | 0 ...updateEntityWithResponseReplaceAsync.json} | 0 ...pdateEntityWithResponseSubclassAsync.json} | 0 35 files changed, 153 insertions(+), 20 deletions(-) rename sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/{TablesAsyncClientTest.java => TableAsyncClientTest.java} (99%) rename sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/{TablesClientBuilderTest.java => TableClientBuilderTest.java} (61%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.createEntityAsync.json => TableAsyncClientTest.createEntityAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.createEntitySubclassAsync.json => TableAsyncClientTest.createEntitySubclassAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.createEntityWithAllSupportedDataTypesAsync.json => TableAsyncClientTest.createEntityWithAllSupportedDataTypesAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.createEntityWithResponseAsync.json => TableAsyncClientTest.createEntityWithResponseAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.createTableAsync.json => TableAsyncClientTest.createTableAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.createTableWithResponseAsync.json => TableAsyncClientTest.createTableWithResponseAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.deleteEntityAsync.json => TableAsyncClientTest.deleteEntityAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.deleteEntityWithResponseAsync.json => TableAsyncClientTest.deleteEntityWithResponseAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.deleteEntityWithResponseMatchETagAsync.json => TableAsyncClientTest.deleteEntityWithResponseMatchETagAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.deleteNonExistingEntityAsync.json => TableAsyncClientTest.deleteNonExistingEntityAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.deleteNonExistingEntityWithResponseAsync.json => TableAsyncClientTest.deleteNonExistingEntityWithResponseAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.deleteNonExistingTableAsync.json => TableAsyncClientTest.deleteNonExistingTableAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.deleteNonExistingTableWithResponseAsync.json => TableAsyncClientTest.deleteNonExistingTableWithResponseAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.deleteTableAsync.json => TableAsyncClientTest.deleteTableAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.deleteTableWithResponseAsync.json => TableAsyncClientTest.deleteTableWithResponseAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.getEntityWithResponseAsync.json => TableAsyncClientTest.getEntityWithResponseAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.getEntityWithResponseSubclassAsync.json => TableAsyncClientTest.getEntityWithResponseSubclassAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.getEntityWithResponseWithSelectAsync.json => TableAsyncClientTest.getEntityWithResponseWithSelectAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.listEntitiesAsync.json => TableAsyncClientTest.listEntitiesAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.listEntitiesSubclassAsync.json => TableAsyncClientTest.listEntitiesSubclassAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.listEntitiesWithFilterAsync.json => TableAsyncClientTest.listEntitiesWithFilterAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.listEntitiesWithSelectAsync.json => TableAsyncClientTest.listEntitiesWithSelectAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.listEntitiesWithTopAsync.json => TableAsyncClientTest.listEntitiesWithTopAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.submitTransactionAsync.json => TableAsyncClientTest.submitTransactionAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.submitTransactionAsyncAllActions.json => TableAsyncClientTest.submitTransactionAsyncAllActions.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.submitTransactionAsyncWithDifferentPartitionKeys.json => TableAsyncClientTest.submitTransactionAsyncWithDifferentPartitionKeys.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.submitTransactionAsyncWithFailingAction.json => TableAsyncClientTest.submitTransactionAsyncWithFailingAction.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.submitTransactionAsyncWithSameRowKeys.json => TableAsyncClientTest.submitTransactionAsyncWithSameRowKeys.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.updateEntityWithResponseMergeAsync.json => TableAsyncClientTest.updateEntityWithResponseMergeAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.updateEntityWithResponseReplaceAsync.json => TableAsyncClientTest.updateEntityWithResponseReplaceAsync.json} (100%) rename sdk/tables/azure-data-tables/src/test/resources/session-records/{TablesAsyncClientTest.updateEntityWithResponseSubclassAsync.json => TableAsyncClientTest.updateEntityWithResponseSubclassAsync.json} (100%) diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesAsyncClientTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableAsyncClientTest.java similarity index 99% rename from sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesAsyncClientTest.java rename to sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableAsyncClientTest.java index 640ed3fce28d0..518e6cbfbebf7 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesAsyncClientTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableAsyncClientTest.java @@ -43,7 +43,7 @@ /** * Tests {@link TableAsyncClient}. */ -public class TablesAsyncClientTest extends TestBase { +public class TableAsyncClientTest extends TestBase { private static final Duration TIMEOUT = Duration.ofSeconds(100); private TableAsyncClient tableClient; diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesClientBuilderTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableClientBuilderTest.java similarity index 61% rename from sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesClientBuilderTest.java rename to sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableClientBuilderTest.java index ee027c51e7f2f..c674c4e833255 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TablesClientBuilderTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableClientBuilderTest.java @@ -21,12 +21,13 @@ import java.security.SecureRandom; import java.util.Collections; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -public class TablesClientBuilderTest { +public class TableClientBuilderTest { private String tableName; private String connectionString; private TableServiceVersion serviceVersion; @@ -195,16 +196,87 @@ public void addPerCallPolicy() { @Test public void multipleFormsOfAuthenticationPresent() { - TableClientBuilder tableClientBuilder = new TableClientBuilder().sasToken("sasToken"); + assertThrows(IllegalStateException.class, () -> new TableClientBuilder() + .sasToken("sasToken") + .credential(new AzureNamedKeyCredential("name", "key")) + .tableName("myTable") + .endpoint("https://myaccount.table.core.windows.net") + .buildAsyncClient()); + + assertThrows(IllegalStateException.class, () -> new TableClientBuilder() + .sasToken("sasToken") + .credential(new AzureSasCredential("sasToken")) + .tableName("myTable") + .endpoint("https://myaccount.table.core.windows.net") + .buildAsyncClient()); + + assertThrows(IllegalStateException.class, () -> new TableClientBuilder() + .credential(new AzureNamedKeyCredential("name", "key")) + .credential(new AzureSasCredential("sasToken")) + .tableName("myTable") + .endpoint("https://myaccount.table.core.windows.net") + .buildAsyncClient()); + + assertThrows(IllegalStateException.class, () -> new TableClientBuilder() + .sasToken("sasToken") + .credential(new AzureNamedKeyCredential("name", "key")) + .credential(new AzureSasCredential("sasToken")) + .tableName("myTable") + .endpoint("https://myaccount.table.core.windows.net") + .buildAsyncClient()); + } + + @Test + public void buildWithSameSasTokenInConnectionStringDoesNotThrow() { + assertDoesNotThrow(() -> new TableClientBuilder() + .sasToken("sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .tableName("myTable") + .buildAsyncClient()); + } - assertThrows(IllegalStateException.class, () -> tableClientBuilder.connectionString(connectionString)); - assertThrows(IllegalStateException.class, - () -> tableClientBuilder.credential(new AzureNamedKeyCredential("name", "key"))); - assertThrows(IllegalStateException.class, - () -> tableClientBuilder.credential(new AzureSasCredential("sasToken"))); + @Test + public void buildWithSameSasTokenInConnectionStringWithPermissionsInDifferentOrderDoesNotThrow() { + assertDoesNotThrow(() -> new TableClientBuilder() + .sasToken("sv=2020-02-10&ss=t&srt=o&sp=lacurwd&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .tableName("myTable") + .buildAsyncClient()); + } - TableClientBuilder tableClientBuilder2 = new TableClientBuilder().connectionString(connectionString); + @Test + public void buildWithDifferentSasTokenInConnectionStringThrows() { + assertThrows(IllegalStateException.class, () -> new TableClientBuilder() + .sasToken("sv=2020-02-10&ss=t&srt=o&sp=rwd&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .tableName("myTable") + .buildAsyncClient()); + } - assertThrows(IllegalStateException.class, () -> tableClientBuilder2.sasToken("sasToken")); + @Test + public void buildWithSameEndpointInConnectionStringDoesNotThrow() { + assertDoesNotThrow(() -> new TableClientBuilder() + .endpoint("https://myaccount.table.core.windows.net/") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .tableName("myTable") + .buildAsyncClient()); + } + + @Test + public void buildWithSameEndpointInConnectionStringWithTrailingSlashDoesNotThrow() { + assertDoesNotThrow(() -> new TableClientBuilder() + .endpoint("https://myaccount.table.core.windows.net") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .tableName("myTable") + .buildAsyncClient()); + } + + @Test + public void buildWithDifferentEndpointInConnectionStringThrows() { + assertThrows(IllegalStateException.class, () -> new TableClientBuilder() + .endpoint("https://myotheraccount.table.core.windows.net/") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .tableName("myTable") + .buildAsyncClient()); } } diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java index 4e95402734c27..3f5c21779cc63 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java @@ -277,6 +277,6 @@ void serviceGetTableClientAsync() { TableAsyncClient tableClient = serviceClient.getTableClient(tableName); // Act & Assert - TablesAsyncClientTest.getEntityWithResponseAsyncImpl(tableClient, this.testResourceNamer); + TableAsyncClientTest.getEntityWithResponseAsyncImpl(tableClient, this.testResourceNamer); } } diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java index ff77114c9fead..fb353fe52af32 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java @@ -21,6 +21,7 @@ import java.security.SecureRandom; import java.util.Collections; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -186,17 +187,77 @@ public void addPerCallPolicy() { @Test public void multipleFormsOfAuthenticationPresent() { - TableServiceClientBuilder tableServiceClientBuilder = new TableServiceClientBuilder().sasToken("sasToken"); + assertThrows(IllegalStateException.class, () -> new TableServiceClientBuilder() + .sasToken("sasToken") + .credential(new AzureNamedKeyCredential("name", "key")) + .endpoint("https://myaccount.table.core.windows.net") + .buildAsyncClient()); + + assertThrows(IllegalStateException.class, () -> new TableServiceClientBuilder() + .sasToken("sasToken") + .credential(new AzureSasCredential("sasToken")) + .endpoint("https://myaccount.table.core.windows.net") + .buildAsyncClient()); + + assertThrows(IllegalStateException.class, () -> new TableServiceClientBuilder() + .credential(new AzureNamedKeyCredential("name", "key")) + .credential(new AzureSasCredential("sasToken")) + .endpoint("https://myaccount.table.core.windows.net") + .buildAsyncClient()); + + assertThrows(IllegalStateException.class, () -> new TableServiceClientBuilder() + .sasToken("sasToken") + .credential(new AzureNamedKeyCredential("name", "key")) + .credential(new AzureSasCredential("sasToken")) + .endpoint("https://myaccount.table.core.windows.net") + .buildAsyncClient()); + } + + @Test + public void buildWithSameSasTokenInConnectionStringDoesNotThrow() { + assertDoesNotThrow(() -> new TableServiceClientBuilder() + .sasToken("sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .buildAsyncClient()); + } - assertThrows(IllegalStateException.class, () -> tableServiceClientBuilder.connectionString(connectionString)); - assertThrows(IllegalStateException.class, - () -> tableServiceClientBuilder.credential(new AzureNamedKeyCredential("name", "key"))); - assertThrows(IllegalStateException.class, - () -> tableServiceClientBuilder.credential(new AzureSasCredential("sasToken"))); + @Test + public void buildWithSameSasTokenInConnectionStringWithPermissionsInDifferentOrderDoesNotThrow() { + assertDoesNotThrow(() -> new TableServiceClientBuilder() + .sasToken("sv=2020-02-10&ss=t&srt=o&sp=lacurwd&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .buildAsyncClient()); + } - TableServiceClientBuilder tableServiceClientBuilder2 = - new TableServiceClientBuilder().connectionString(connectionString); + @Test + public void buildWithDifferentSasTokenInConnectionStringThrows() { + assertThrows(IllegalStateException.class, () -> new TableServiceClientBuilder() + .sasToken("sv=2020-02-10&ss=t&srt=o&sp=rwd&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .buildAsyncClient()); + } - assertThrows(IllegalStateException.class, () -> tableServiceClientBuilder2.sasToken("sasToken")); + @Test + public void buildWithSameEndpointInConnectionStringDoesNotThrow() { + assertDoesNotThrow(() -> new TableServiceClientBuilder() + .endpoint("https://myaccount.table.core.windows.net/") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .buildAsyncClient()); + } + + @Test + public void buildWithSameEndpointInConnectionStringWithTrailingSlashDoesNotThrow() { + assertDoesNotThrow(() -> new TableServiceClientBuilder() + .endpoint("https://myaccount.table.core.windows.net") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .buildAsyncClient()); + } + + @Test + public void buildWithDifferentEndpointInConnectionStringThrows() { + assertThrows(IllegalStateException.class, () -> new TableServiceClientBuilder() + .endpoint("https://myotheraccount.table.core.windows.net/") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .buildAsyncClient()); } } diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createEntityAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createEntityAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createEntityAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createEntityAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createEntitySubclassAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createEntitySubclassAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createEntitySubclassAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createEntitySubclassAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createEntityWithAllSupportedDataTypesAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createEntityWithAllSupportedDataTypesAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createEntityWithAllSupportedDataTypesAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createEntityWithAllSupportedDataTypesAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createEntityWithResponseAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createEntityWithResponseAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createEntityWithResponseAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createEntityWithResponseAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createTableAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createTableAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createTableAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createTableAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createTableWithResponseAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createTableWithResponseAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.createTableWithResponseAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.createTableWithResponseAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteEntityAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteEntityAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteEntityAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteEntityAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteEntityWithResponseAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteEntityWithResponseAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteEntityWithResponseAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteEntityWithResponseAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteEntityWithResponseMatchETagAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteEntityWithResponseMatchETagAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteEntityWithResponseMatchETagAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteEntityWithResponseMatchETagAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteNonExistingEntityAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteNonExistingEntityAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteNonExistingEntityAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteNonExistingEntityAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteNonExistingEntityWithResponseAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteNonExistingEntityWithResponseAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteNonExistingEntityWithResponseAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteNonExistingEntityWithResponseAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteNonExistingTableAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteNonExistingTableAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteNonExistingTableAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteNonExistingTableAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteNonExistingTableWithResponseAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteNonExistingTableWithResponseAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteNonExistingTableWithResponseAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteNonExistingTableWithResponseAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteTableAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteTableAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteTableAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteTableAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteTableWithResponseAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteTableWithResponseAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.deleteTableWithResponseAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.deleteTableWithResponseAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.getEntityWithResponseAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.getEntityWithResponseAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.getEntityWithResponseAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.getEntityWithResponseAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.getEntityWithResponseSubclassAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.getEntityWithResponseSubclassAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.getEntityWithResponseSubclassAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.getEntityWithResponseSubclassAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.getEntityWithResponseWithSelectAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.getEntityWithResponseWithSelectAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.getEntityWithResponseWithSelectAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.getEntityWithResponseWithSelectAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesSubclassAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesSubclassAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesSubclassAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesSubclassAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesWithFilterAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesWithFilterAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesWithFilterAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesWithFilterAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesWithSelectAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesWithSelectAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesWithSelectAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesWithSelectAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesWithTopAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesWithTopAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.listEntitiesWithTopAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.listEntitiesWithTopAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsyncAllActions.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsyncAllActions.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsyncAllActions.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsyncAllActions.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsyncWithDifferentPartitionKeys.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsyncWithDifferentPartitionKeys.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsyncWithDifferentPartitionKeys.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsyncWithDifferentPartitionKeys.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsyncWithFailingAction.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsyncWithFailingAction.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsyncWithFailingAction.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsyncWithFailingAction.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsyncWithSameRowKeys.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsyncWithSameRowKeys.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.submitTransactionAsyncWithSameRowKeys.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.submitTransactionAsyncWithSameRowKeys.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.updateEntityWithResponseMergeAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.updateEntityWithResponseMergeAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.updateEntityWithResponseMergeAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.updateEntityWithResponseMergeAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.updateEntityWithResponseReplaceAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.updateEntityWithResponseReplaceAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.updateEntityWithResponseReplaceAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.updateEntityWithResponseReplaceAsync.json diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.updateEntityWithResponseSubclassAsync.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.updateEntityWithResponseSubclassAsync.json similarity index 100% rename from sdk/tables/azure-data-tables/src/test/resources/session-records/TablesAsyncClientTest.updateEntityWithResponseSubclassAsync.json rename to sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.updateEntityWithResponseSubclassAsync.json From 08e581da7a0c916a29d4f6134e6ff65bf55f76b9 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 3 Jun 2021 19:34:31 -0500 Subject: [PATCH 17/25] Updated CHANGELOG and client builders' JavaDoc. --- sdk/tables/azure-data-tables/CHANGELOG.md | 3 +-- .../java/com/azure/data/tables/TableClientBuilder.java | 9 +++++++-- .../com/azure/data/tables/TableServiceClientBuilder.java | 9 +++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/sdk/tables/azure-data-tables/CHANGELOG.md b/sdk/tables/azure-data-tables/CHANGELOG.md index a0ab2d5bfd58c..91aaa8e4ba74a 100644 --- a/sdk/tables/azure-data-tables/CHANGELOG.md +++ b/sdk/tables/azure-data-tables/CHANGELOG.md @@ -23,8 +23,7 @@ - `updateEntity(TableEntity entity, TableEntityUpdateMode updateMode, boolean ifUnchanged)` - `getEntity(String partitionKey, String rowKey, List select)` -- Client builders now throw an `IllegalStateException` when calling `buildClient()` or `buildAsyncClient()` if more than one of `credential(AzureNamedKeyCredential)`, `credential(AzureSasCredential)`, `sasToken(String)` and `connectionString(String)` were used, as providing more than one form of authentication is not allowed. -- Client builders now throw an `IllegalStateException` when calling `buildClient()` or `buildAsyncClient()` if no endpoint or form of authentication has been set. +- Client builders now also throw an `IllegalStateException` when calling `buildClient()` and `buildAsyncClient()` if multiple forms of authentication are provided, with the exception of `sasToken` + `connectionString`; or if `endpoint` and/or `sasToken` are set alongside a `connectionString` and the endpoint and/or SAS token in the latter are different than the former, respectively. ## 12.0.0-beta.7 (2021-05-15) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java index 7aa2a6f5d53bd..b21877a735a67 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClientBuilder.java @@ -70,7 +70,11 @@ public TableClientBuilder() { * * @throws NullPointerException If {@code endpoint} or {@code tableName} are {@code null}. * @throws IllegalArgumentException If {@code endpoint} is malformed or empty or if {@code tableName} is empty. - * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified. + * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified or if + * multiple forms of authentication are provided, with the exception of {@code sasToken} + + * {@code connectionString}. Also thrown if {@code endpoint} and/or {@code sasToken} are set alongside a + * {@code connectionString} and the endpoint and/or SAS token in the latter are different than the former, + * respectively. */ public TableClient buildClient() { return new TableClient(buildAsyncClient()); @@ -86,7 +90,8 @@ public TableClient buildClient() { * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified or if * multiple forms of authentication are provided, with the exception of {@code sasToken} + * {@code connectionString}. Also thrown if {@code endpoint} and/or {@code sasToken} are set alongside a - * {@code connectionString} and the endpoint and/or SAS token in the latter are different, respectively. + * {@code connectionString} and the endpoint and/or SAS token in the latter are different than the former, + * respectively. */ public TableAsyncClient buildAsyncClient() { TableServiceVersion serviceVersion = version != null ? version : TableServiceVersion.getLatest(); diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java index b2baae5e12d3e..d139a0f88b635 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClientBuilder.java @@ -66,7 +66,11 @@ public TableServiceClientBuilder() { * * @throws NullPointerException If {@code endpoint} is {@code null}. * @throws IllegalArgumentException If {@code endpoint} is malformed or empty. - * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified. + * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified or if + * multiple forms of authentication are provided, with the exception of {@code sasToken} + + * {@code connectionString}. Also thrown if {@code endpoint} and/or {@code sasToken} are set alongside a + * {@code connectionString} and the endpoint and/or SAS token in the latter are different than the former, + * respectively. */ public TableServiceClient buildClient() { return new TableServiceClient(buildAsyncClient()); @@ -82,7 +86,8 @@ public TableServiceClient buildClient() { * @throws IllegalStateException If no form of authentication or {@code endpoint} have been specified or if * multiple forms of authentication are provided, with the exception of {@code sasToken} + * {@code connectionString}. Also thrown if {@code endpoint} and/or {@code sasToken} are set alongside a - * {@code connectionString} and the endpoint and/or SAS token in the latter are different, respectively. + * {@code connectionString} and the endpoint and/or SAS token in the latter are different than the former, + * respectively. */ public TableServiceAsyncClient buildAsyncClient() { TableServiceVersion serviceVersion = version != null ? version : TableServiceVersion.getLatest(); From fc1cdf303503370988c9c70fb5c2d1add524dab1 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 3 Jun 2021 19:39:57 -0500 Subject: [PATCH 18/25] Applied APIView feedback. --- .../azure/data/tables/TableAsyncClient.java | 36 +++++------------- .../com/azure/data/tables/TableClient.java | 38 +++++-------------- .../data/tables/TableServiceAsyncClient.java | 19 ---------- .../azure/data/tables/TableServiceClient.java | 19 ---------- .../azure/data/tables/models/TableItem.java | 2 + .../data/tables/models/TableServiceError.java | 3 ++ .../tables/models/TableTransactionAction.java | 5 ++- 7 files changed, 28 insertions(+), 94 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java index 39fb8c2c53102..33d3b65397797 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableAsyncClient.java @@ -213,24 +213,6 @@ public TableServiceVersion getServiceVersion() { * {@link AzureNamedKeyCredential}. */ public String generateSas(TableSasSignatureValues tableSasSignatureValues) { - return generateSas(tableSasSignatureValues, Context.NONE); - } - - /** - * Generates a service SAS for the table using the specified {@link TableSasSignatureValues}. - * - *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} - *

See {@link TableSasSignatureValues} for more information on how to construct a service SAS.

- * - * @param tableSasSignatureValues {@link TableSasSignatureValues} - * @param context Additional context that is passed through the code when generating a SAS. - * - * @return A {@code String} representing the SAS query parameters. - * - * @throws IllegalStateException If this {@link TableAsyncClient} is not authenticated with an - * {@link AzureNamedKeyCredential}. - */ - public String generateSas(TableSasSignatureValues tableSasSignatureValues, Context context) { AzureNamedKeyCredential azureNamedKeyCredential = TableSasUtils.extractNamedKeyCredential(getHttpPipeline()); if (azureNamedKeyCredential == null) { @@ -867,11 +849,11 @@ Mono> getEntityWithResponse(String partition * {@link TableSignedIdentifier access policies}. */ @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedFlux getAccessPolicy() { - return (PagedFlux) fluxContext(this::getAccessPolicy); + public PagedFlux listAccessPolicies() { + return (PagedFlux) fluxContext(this::listAccessPolicies); } - PagedFlux getAccessPolicy(Context context) { + PagedFlux listAccessPolicies(Context context) { context = context == null ? Context.NONE : context; try { @@ -916,8 +898,8 @@ private TableAccessPolicy toTableAccessPolicy(AccessPolicy accessPolicy) { * @return An empty reactive result. */ @ServiceMethod(returns = ReturnType.SINGLE) - public Mono setAccessPolicy(List tableSignedIdentifiers) { - return this.setAccessPolicyWithResponse(tableSignedIdentifiers).flatMap(FluxUtil::toMono); + public Mono setAccessPolicies(List tableSignedIdentifiers) { + return this.setAccessPoliciesWithResponse(tableSignedIdentifiers).flatMap(FluxUtil::toMono); } /** @@ -929,12 +911,12 @@ public Mono setAccessPolicy(List tableSignedIdentif * @return A reactive result containing the HTTP response. */ @ServiceMethod(returns = ReturnType.SINGLE) - public Mono> setAccessPolicyWithResponse(List tableSignedIdentifiers) { - return withContext(context -> this.setAccessPolicyWithResponse(tableSignedIdentifiers, context)); + public Mono> setAccessPoliciesWithResponse(List tableSignedIdentifiers) { + return withContext(context -> this.setAccessPoliciesWithResponse(tableSignedIdentifiers, context)); } - Mono> setAccessPolicyWithResponse(List tableSignedIdentifiers, - Context context) { + Mono> setAccessPoliciesWithResponse(List tableSignedIdentifiers, + Context context) { context = context == null ? Context.NONE : context; try { diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java index fca2b53745a17..ecd189746b233 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableClient.java @@ -96,25 +96,7 @@ public TableServiceVersion getServiceVersion() { * {@link AzureNamedKeyCredential}. */ public String generateSas(TableSasSignatureValues tableSasSignatureValues) { - return client.generateSas(tableSasSignatureValues, Context.NONE); - } - - /** - * Generates a service SAS for the table using the specified {@link TableSasSignatureValues}. - * - *

Note : The client must be authenticated via {@link AzureNamedKeyCredential} - *

See {@link TableSasSignatureValues} for more information on how to construct a service SAS.

- * - * @param tableSasSignatureValues {@link TableSasSignatureValues} - * @param context Additional context that is passed through the code when generating a SAS. - * - * @return A {@code String} representing the SAS query parameters. - * - * @throws IllegalStateException If this {@link TableClient} is not authenticated with an - * {@link AzureNamedKeyCredential}. - */ - public String generateSas(TableSasSignatureValues tableSasSignatureValues, Context context) { - return client.generateSas(tableSasSignatureValues, context); + return client.generateSas(tableSasSignatureValues); } /** @@ -433,8 +415,8 @@ public Response getEntityWithResponse(String partitionKey, String r * {@link TableSignedIdentifier access policies}. */ @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedIterable getAccessPolicy() { - return new PagedIterable<>(client.getAccessPolicy()); + public PagedIterable listAccessPolicies() { + return new PagedIterable<>(client.listAccessPolicies()); } /** @@ -448,8 +430,8 @@ public PagedIterable getAccessPolicy() { * {@link TableSignedIdentifier access policies}. */ @ServiceMethod(returns = ReturnType.COLLECTION) - public PagedIterable getAccessPolicy(Duration timeout, Context context) { - return new PagedIterable<>(applyOptionalTimeout(client.getAccessPolicy(context), timeout)); + public PagedIterable listAccessPolicies(Duration timeout, Context context) { + return new PagedIterable<>(applyOptionalTimeout(client.listAccessPolicies(context), timeout)); } /** @@ -458,8 +440,8 @@ public PagedIterable getAccessPolicy(Duration timeout, Co * @param tableSignedIdentifiers The {@link TableSignedIdentifier access policies} for the table. */ @ServiceMethod(returns = ReturnType.SINGLE) - public void setAccessPolicy(List tableSignedIdentifiers) { - client.setAccessPolicy(tableSignedIdentifiers).block(); + public void setAccessPolicies(List tableSignedIdentifiers) { + client.setAccessPolicies(tableSignedIdentifiers).block(); } /** @@ -473,9 +455,9 @@ public void setAccessPolicy(List tableSignedIdentifiers) * @return The HTTP response. */ @ServiceMethod(returns = ReturnType.SINGLE) - public Response setAccessPolicyWithResponse(List tableSignedIdentifiers, - Duration timeout, Context context) { - return blockWithOptionalTimeout(client.setAccessPolicyWithResponse(tableSignedIdentifiers, context), timeout); + public Response setAccessPoliciesWithResponse(List tableSignedIdentifiers, + Duration timeout, Context context) { + return blockWithOptionalTimeout(client.setAccessPoliciesWithResponse(tableSignedIdentifiers, context), timeout); } /** diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java index 7bfae300b6917..180a167db98aa 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceAsyncClient.java @@ -166,25 +166,6 @@ public TableServiceVersion getServiceVersion() { * {@link AzureNamedKeyCredential}. */ public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues) { - return generateAccountSas(tableAccountSasSignatureValues, Context.NONE); - } - - /** - * Generates an account SAS for the Azure Storage account using the specified - * {@link TableAccountSasSignatureValues}. - * - *

Note : The client must be authenticated via {@link AzureNamedKeyCredential}. - *

See {@link TableAccountSasSignatureValues} for more information on how to construct an account SAS.

- * - * @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. - * - * @throws IllegalStateException If this {@link TableClient} is not authenticated with an - * {@link AzureNamedKeyCredential}. - */ - public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues, Context context) { AzureNamedKeyCredential azureNamedKeyCredential = TableSasUtils.extractNamedKeyCredential(getHttpPipeline()); if (azureNamedKeyCredential == null) { diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java index df7f39ce97a9c..f7bc25e59e7e8 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/TableServiceClient.java @@ -91,25 +91,6 @@ public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasS return client.generateAccountSas(tableAccountSasSignatureValues); } - /** - * Generates an account SAS for the Azure Storage account using the specified - * {@link TableAccountSasSignatureValues}. - * - *

Note : The client must be authenticated via {@link AzureNamedKeyCredential}. - *

See {@link TableAccountSasSignatureValues} for more information on how to construct an account SAS.

- * - * @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. - * - * @throws IllegalStateException If this {@link TableClient} is not authenticated with an - * {@link AzureNamedKeyCredential}. - */ - public String generateAccountSas(TableAccountSasSignatureValues tableAccountSasSignatureValues, Context context) { - return client.generateAccountSas(tableAccountSasSignatureValues, context); - } - /** * Gets a {@link TableClient} instance for the provided table in the account. * diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableItem.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableItem.java index 5fdcd8bd2388b..d240add3d4efc 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableItem.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableItem.java @@ -2,12 +2,14 @@ // Licensed under the MIT License. package com.azure.data.tables.models; +import com.azure.core.annotation.Immutable; import com.azure.data.tables.implementation.ModelHelper; import com.azure.data.tables.implementation.models.TableResponseProperties; /** * A table within a storage or CosmosDB table API account. */ +@Immutable public final class TableItem { private final String name; private final String odataType; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableServiceError.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableServiceError.java index 8dd84cbd9e292..930d88497fe18 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableServiceError.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableServiceError.java @@ -2,9 +2,12 @@ // Licensed under the MIT License. package com.azure.data.tables.models; +import com.azure.core.annotation.Immutable; + /** * A class that represents an error occurred in a Tables operation. */ +@Immutable public final class TableServiceError { /* * The service error code. diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionAction.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionAction.java index 05c7db6654aa6..5e5f35823179f 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionAction.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionAction.java @@ -2,10 +2,13 @@ // Licensed under the MIT License. package com.azure.data.tables.models; +import com.azure.core.annotation.Immutable; + /** * Defines an action to be included as part of a transactional batch operation. */ -public class TableTransactionAction { +@Immutable +public final class TableTransactionAction { private final TableTransactionActionType actionType; private final TableEntity entity; private final boolean ifUnchanged; From 893da5f7901a274e3c61627e499b38967929a740 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 3 Jun 2021 19:47:22 -0500 Subject: [PATCH 19/25] Updated CHANGELOG again. --- sdk/tables/azure-data-tables/CHANGELOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sdk/tables/azure-data-tables/CHANGELOG.md b/sdk/tables/azure-data-tables/CHANGELOG.md index 91aaa8e4ba74a..efc7ee0e3f981 100644 --- a/sdk/tables/azure-data-tables/CHANGELOG.md +++ b/sdk/tables/azure-data-tables/CHANGELOG.md @@ -5,7 +5,18 @@ ### 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. +- Added support for generating SAS tokens at the Account and Table Service in all clients. +- Added the following methods to `TableClient`, `TableAsyncClient`: + - `listAccessPolicies()` + - `setAccessPolicies()` + - `setAccessPoliciesWithResponse()` +- Added the following methods to `TableServiceClient`, `TableServiceAsyncClient`: + - `getProperties()` + - `getPropertiesWithResponse()` + - `setProperties()` + - `setPropertiesWithResponse()` + - `getStatistics()` + - `getStatisticsWithResponse()` ### Breaking Changes From d52aa73d3947345970d5cd05a2f18875302ac814 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Thu, 3 Jun 2021 20:21:40 -0500 Subject: [PATCH 20/25] Removed unused imports. Simplified SAS token comparison logic. --- .../com/azure/data/tables/BuilderHelper.java | 4 +- .../implementation/TableSasGenerator.java | 1 - .../tables/implementation/TableSasUtils.java | 2 - .../tables/implementation/TableUtils.java | 194 ------------------ .../data/tables/TableClientBuilderTest.java | 11 +- .../tables/TableServiceClientBuilderTest.java | 10 +- 6 files changed, 3 insertions(+), 219 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java index b89effa451685..7d4b9260192e8 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java @@ -37,8 +37,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.azure.data.tables.implementation.TableUtils.sasTokenEquals; - final class BuilderHelper { private static final Map PROPERTIES = CoreUtils.getProperties("azure-data-tables.properties"); @@ -146,7 +144,7 @@ static void validateCredentials(AzureNamedKeyCredential azureNamedKeyCredential, StorageAuthenticationSettings authSettings = storageConnectionString.getStorageAuthSettings(); if (authSettings.getType() == StorageAuthenticationSettings.Type.SAS_TOKEN) { - if (sasTokenEquals(sasToken, authSettings.getSasToken())) { + if (sasToken.equals(authSettings.getSasToken())) { return; } else { throw logger.logExceptionAsError(new IllegalStateException("'connectionString' contains a SAS token" diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java index 2156305333483..a31fded8261dc 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java @@ -3,7 +3,6 @@ package com.azure.data.tables.implementation; import com.azure.core.credential.AzureNamedKeyCredential; -import com.azure.core.util.Context; import com.azure.core.util.logging.ClientLogger; import com.azure.data.tables.TableServiceVersion; import com.azure.data.tables.sas.TableSasIpRange; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java index b88b61493ea63..751a1637d0aa8 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasUtils.java @@ -4,8 +4,6 @@ import com.azure.core.credential.AzureNamedKeyCredential; import com.azure.core.http.HttpPipeline; -import com.azure.core.util.Context; -import com.azure.core.util.logging.ClientLogger; import com.azure.data.tables.TableAzureNamedKeyCredentialPolicy; import javax.crypto.Mac; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java index 48d5e1b6911ef..b5d7b550b5e17 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableUtils.java @@ -8,7 +8,6 @@ import com.azure.core.http.rest.Response; import com.azure.core.http.rest.SimpleResponse; import com.azure.core.util.CoreUtils; -import com.azure.core.util.UrlBuilder; import com.azure.core.util.logging.ClientLogger; import com.azure.data.tables.implementation.models.TableServiceErrorException; import com.azure.data.tables.implementation.models.TableServiceErrorOdataError; @@ -19,18 +18,9 @@ import reactor.core.publisher.Mono; import java.io.UnsupportedEncodingException; -import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; import java.time.Duration; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; @@ -42,24 +32,6 @@ */ public final class TableUtils { private static final String UTF8_CHARSET = "UTF-8"; - private static final String INVALID_DATE_STRING = "Invalid Date String: %s."; - /** - * Stores a reference to the date/time pattern with the greatest precision Java.util.Date is capable of expressing. - */ - private static final String MAX_PRECISION_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; - /** - * Stores a reference to the ISO8601 date/time pattern. - */ - private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - /** - * Stores a reference to the ISO8601 date/time pattern. - */ - private static final String ISO8601_PATTERN_NO_SECONDS = "yyyy-MM-dd'T'HH:mm'Z'"; - /** - * The length of a datestring that matches the MAX_PRECISION_PATTERN. - */ - private static final int MAX_PRECISION_DATESTRING_LENGTH = MAX_PRECISION_PATTERN.replaceAll("'", "") - .length(); private TableUtils() { throw new UnsupportedOperationException("Cannot instantiate TablesUtils"); @@ -346,170 +318,4 @@ private static String encode(final String stringToEncode) { throw new RuntimeException(ex); } } - - /** - * Performs a safe encoding of a url string, only encoding the path. - * - * @param url The url to encode. - * @return The encoded url. - */ - public static String encodeUrlPath(String url) { - /* Deconstruct the URL and reconstruct it making sure the path is encoded. */ - UrlBuilder builder = UrlBuilder.parse(url); - String path = builder.getPath(); - if (path.startsWith("/")) { - path = path.substring(1); - } - path = urlEncode(urlDecode(path)); - builder.setPath(path); - return builder.toString(); - } - - /** - * Given a String representing a date in a form of the ISO8601 pattern, generates a Date representing it with up to - * millisecond precision. - * - * @param dateString the {@code String} to be interpreted as a Date - * @return the corresponding Date object - * @throws IllegalArgumentException If {@code dateString} doesn't match an ISO8601 pattern - */ - public static OffsetDateTime parseDate(String dateString) { - String pattern = MAX_PRECISION_PATTERN; - switch (dateString.length()) { - case 28: // "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"-> [2012-01-04T23:21:59.1234567Z] length = 28 - case 27: // "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"-> [2012-01-04T23:21:59.123456Z] length = 27 - case 26: // "yyyy-MM-dd'T'HH:mm:ss.SSSSS'Z'"-> [2012-01-04T23:21:59.12345Z] length = 26 - case 25: // "yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'"-> [2012-01-04T23:21:59.1234Z] length = 25 - case 24: // "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"-> [2012-01-04T23:21:59.123Z] length = 24 - dateString = dateString.substring(0, MAX_PRECISION_DATESTRING_LENGTH); - break; - case 23: // "yyyy-MM-dd'T'HH:mm:ss.SS'Z'"-> [2012-01-04T23:21:59.12Z] length = 23 - // SS is assumed to be milliseconds, so a trailing 0 is necessary - dateString = dateString.replace("Z", "0"); - break; - case 22: // "yyyy-MM-dd'T'HH:mm:ss.S'Z'"-> [2012-01-04T23:21:59.1Z] length = 22 - // S is assumed to be milliseconds, so trailing 0's are necessary - dateString = dateString.replace("Z", "00"); - break; - case 20: // "yyyy-MM-dd'T'HH:mm:ss'Z'"-> [2012-01-04T23:21:59Z] length = 20 - pattern = ISO8601_PATTERN; - break; - case 17: // "yyyy-MM-dd'T'HH:mm'Z'"-> [2012-01-04T23:21Z] length = 17 - pattern = ISO8601_PATTERN_NO_SECONDS; - break; - default: - throw new IllegalArgumentException(String.format(Locale.ROOT, INVALID_DATE_STRING, dateString)); - } - - DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, Locale.ROOT); - return LocalDateTime.parse(dateString, formatter).atZone(ZoneOffset.UTC).toOffsetDateTime(); - } - - /** - * Parses a query string into a one to many TreeMap. - * - * @param queryParams The string of query params to parse. - * @return A {@code HashMap} of the key values. - */ - public static Map parseQueryString(String queryParams) { - final TreeMap retVals = new TreeMap<>(Comparator.naturalOrder()); - - if (CoreUtils.isNullOrEmpty(queryParams)) { - return retVals; - } - - // split name value pairs by splitting on the '&' character - final String[] valuePairs = queryParams.split("&"); - - // for each field value pair parse into appropriate map entries - for (String valuePair : valuePairs) { - // Getting key and value for a single query parameter - final int equalDex = valuePair.indexOf("="); - String key = urlDecode(valuePair.substring(0, equalDex)).toLowerCase(Locale.ROOT); - String value = urlDecode(valuePair.substring(equalDex + 1)); - - // add to map - String[] keyValues = retVals.get(key); - - // check if map already contains key - if (keyValues == null) { - // map does not contain this key - keyValues = new String[]{value}; - } else { - // map contains this key already so append - final String[] newValues = new String[keyValues.length + 1]; - System.arraycopy(keyValues, 0, newValues, 0, keyValues.length); - - newValues[newValues.length - 1] = value; - keyValues = newValues; - } - retVals.put(key, keyValues); - } - - return retVals; - } - - public static boolean sasTokenEquals(String sasToken1, String sasToken2) { - List keysToCompareUnsorted = new ArrayList<>(); - keysToCompareUnsorted.add("ss"); // Services - keysToCompareUnsorted.add("srt"); // Resource types - keysToCompareUnsorted.add("sp"); // Permissions - - Map queryParams1 = parseQueryString(sasToken1); - Map queryParams2 = parseQueryString(sasToken2); - - if (queryParams1.size() != queryParams2.size()) { - // More query parameters on one token than the other. - return false; - } - - for (String key : queryParams1.keySet()) { - if (!queryParams2.containsKey(key)) { - // Different query parameters. - return false; - } - - String[] param1 = queryParams1.get(key); - String[] param2 = queryParams2.get(key); - - if (keysToCompareUnsorted.contains(key)) { - for (int i = 0; i < param1.length; i++) { - param1[i] = orderString(param1[i]); - } - - for (int i = 0; i < param2.length; i++) { - param2[i] = orderString(param2[i]); - } - - } - - if (!sasArrayEquals(param1, param2)) { - return false; - } - } - - // SAS tokens are the same. - return true; - } - - private static String orderString(String str) { - char[] charArray = str.toCharArray(); - - Arrays.sort(charArray); - - return new String(charArray); - } - - private static boolean sasArrayEquals(String[] array1, String[] array2) { - if (array1.length > 1) { - Arrays.sort(array1); - } - - if (array2.length > 1) { - Arrays.sort(array2); - } - - // Different values in these query parameters. - return Arrays.equals(array1, array2); - } } diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableClientBuilderTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableClientBuilderTest.java index c674c4e833255..50dbd29adbcf5 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableClientBuilderTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableClientBuilderTest.java @@ -235,20 +235,11 @@ public void buildWithSameSasTokenInConnectionStringDoesNotThrow() { .buildAsyncClient()); } - @Test - public void buildWithSameSasTokenInConnectionStringWithPermissionsInDifferentOrderDoesNotThrow() { - assertDoesNotThrow(() -> new TableClientBuilder() - .sasToken("sv=2020-02-10&ss=t&srt=o&sp=lacurwd&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") - .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") - .tableName("myTable") - .buildAsyncClient()); - } - @Test public void buildWithDifferentSasTokenInConnectionStringThrows() { assertThrows(IllegalStateException.class, () -> new TableClientBuilder() .sasToken("sv=2020-02-10&ss=t&srt=o&sp=rwd&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") - .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=anotherSignature") .tableName("myTable") .buildAsyncClient()); } diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java index fb353fe52af32..8667309445a86 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceClientBuilderTest.java @@ -221,19 +221,11 @@ public void buildWithSameSasTokenInConnectionStringDoesNotThrow() { .buildAsyncClient()); } - @Test - public void buildWithSameSasTokenInConnectionStringWithPermissionsInDifferentOrderDoesNotThrow() { - assertDoesNotThrow(() -> new TableServiceClientBuilder() - .sasToken("sv=2020-02-10&ss=t&srt=o&sp=lacurwd&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") - .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") - .buildAsyncClient()); - } - @Test public void buildWithDifferentSasTokenInConnectionStringThrows() { assertThrows(IllegalStateException.class, () -> new TableServiceClientBuilder() .sasToken("sv=2020-02-10&ss=t&srt=o&sp=rwd&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") - .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=someSignature") + .connectionString("TableEndpoint=https://myaccount.table.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=t&srt=o&sp=rwdlacu&se=2021-06-04T04:45:57Z&st=2021-06-03T20:45:57Z&spr=https&sig=anotherSignature") .buildAsyncClient()); } From 594bac2ea2856862f9817d2eba6207decb81cf38 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 4 Jun 2021 15:01:40 -0500 Subject: [PATCH 21/25] Fixed SAS token generation at the table level. Re-ordered query parameters in SAS tokens for both accounts and tables. Added tests for SAS tokens. --- .../implementation/StorageConstants.java | 5 + .../TableAccountSasGenerator.java | 4 +- .../implementation/TableSasGenerator.java | 12 +- .../data/tables/sas/TableSasPermission.java | 24 +-- .../com/azure/data/tables/SasModelsTest.java | 18 +-- .../data/tables/TableAsyncClientTest.java | 127 +++++++++++++++- .../tables/TableServiceAsyncClientTest.java | 142 +++++++++++++++++- ...anUseSasTokenToCreateValidTableClient.json | 55 +++++++ ...est.generateSasTokenWithAllParameters.json | 29 ++++ ...generateSasTokenWithMinimumParameters.json | 29 ++++ ...anUseSasTokenToCreateValidTableClient.json | 55 +++++++ ...erateAccountSasTokenWithAllParameters.json | 4 + ...eAccountSasTokenWithMinimumParameters.json | 4 + 13 files changed, 477 insertions(+), 31 deletions(-) create mode 100644 sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.canUseSasTokenToCreateValidTableClient.json create mode 100644 sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithAllParameters.json create mode 100644 sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithMinimumParameters.json create mode 100644 sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.canUseSasTokenToCreateValidTableClient.json create mode 100644 sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.generateAccountSasTokenWithAllParameters.json create mode 100644 sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.generateAccountSasTokenWithMinimumParameters.json diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java index 631fedf9fd012..f8c4a7b321a7c 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/StorageConstants.java @@ -329,6 +329,11 @@ private UrlConstants() { */ public static final String SAS_CONTENT_TYPE = "rsct"; + /** + * The SAS table name parameter. + */ + public static final String SAS_TABLE_NAME = "tn"; + /** * The SAS signed object id parameter for user delegation SAS. */ diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java index a957435f5bf9c..941fbe7361f95 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableAccountSasGenerator.java @@ -103,13 +103,13 @@ private String encode(String signature) { tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SERVICE_VERSION, this.version); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SERVICES, this.services); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_RESOURCES_TYPES, this.resourceTypes); - tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_PROTOCOL, this.protocol); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_START_TIME, formatQueryParameterDate(this.startTime)); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_EXPIRY_TIME, formatQueryParameterDate(this.expiryTime)); - tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_PROTOCOL, this.protocol); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNATURE, signature); return sb.toString(); diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java index a31fded8261dc..343fa061fbc10 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/implementation/TableSasGenerator.java @@ -11,6 +11,7 @@ import com.azure.data.tables.sas.TableSasSignatureValues; import java.time.OffsetDateTime; +import java.util.Locale; import java.util.Objects; import static com.azure.data.tables.implementation.TableSasUtils.computeHmac256; @@ -89,19 +90,20 @@ private String encode(String signature) { StringBuilder sb = new StringBuilder(); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SERVICE_VERSION, this.version); - tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_PROTOCOL, this.protocol); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_START_TIME, formatQueryParameterDate(this.startTime)); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_EXPIRY_TIME, formatQueryParameterDate(this.expiryTime)); - tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); - tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNED_IDENTIFIER, this.identifier); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_TABLE_NAME, tableName); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNED_PERMISSIONS, this.permissions); - tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNATURE, signature); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_TABLE_START_PARTITION_KEY, startPartitionKey); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_TABLE_START_ROW_KEY, startRowKey); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_TABLE_END_PARTITION_KEY, endPartitionKey); tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_TABLE_END_ROW_KEY, endRowKey); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_IP_RANGE, this.sasIpRange); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_PROTOCOL, this.protocol); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNED_IDENTIFIER, this.identifier); + tryAppendQueryParameter(sb, StorageConstants.UrlConstants.SAS_SIGNATURE, signature); return sb.toString(); } @@ -162,7 +164,7 @@ private String stringToSign(String canonicalName) { this.permissions == null ? "" : this.permissions, this.startTime == null ? "" : StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.startTime), this.expiryTime == null ? "" : StorageConstants.ISO_8601_UTC_DATE_FORMATTER.format(this.expiryTime), - canonicalName, + canonicalName.toLowerCase(Locale.ROOT), this.identifier == null ? "" : this.identifier, this.sasIpRange == null ? "" : this.sasIpRange.toString(), this.protocol == null ? "" : protocol.toString(), diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java index e5509441d9ace..0b6f672102585 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasPermission.java @@ -27,7 +27,7 @@ public final class TableSasPermission { private boolean readPermission; private boolean addPermission; private boolean updatePermission; - private boolean processPermission; + private boolean deletePermission; /** * Creates a {@link TableSasPermission} from the specified permissions string. This method will throw an @@ -37,7 +37,7 @@ public final class TableSasPermission { * * @return A {@link TableSasPermission} generated from the given {@link String}. * - * @throws IllegalArgumentException If {@code permString} contains a character other than r, a, u, or p. + * @throws IllegalArgumentException If {@code permString} contains a character other than r, a, u, or d. */ public static TableSasPermission parse(String permString) { TableSasPermission permissions = new TableSasPermission(); @@ -57,8 +57,8 @@ public static TableSasPermission parse(String permString) { permissions.updatePermission = true; break; - case 'p': - permissions.processPermission = true; + case 'd': + permissions.deletePermission = true; break; default: @@ -149,24 +149,24 @@ public TableSasPermission setUpdatePermission(boolean hasUpdatePermission) { } /** - * Gets the process permission status. + * Gets the delete permission status. * * @return {@code true} if the SAS has permission to delete entities from the table. {@code false}, otherwise. */ - public boolean hasProcessPermission() { - return processPermission; + public boolean hasDeletePermission() { + return deletePermission; } /** * Sets the process permission status. * - * @param hasProcessPermission {@code true} if the SAS has permission to delete entities from the table. + * @param hasDeletePermission {@code true} if the SAS has permission to delete entities from the table. * {@code false}, otherwise. * * @return The updated {@link TableSasPermission} object. */ - public TableSasPermission setProcessPermission(boolean hasProcessPermission) { - this.processPermission = hasProcessPermission; + public TableSasPermission setDeletePermission(boolean hasDeletePermission) { + this.deletePermission = hasDeletePermission; return this; } @@ -196,8 +196,8 @@ public String toString() { builder.append('u'); } - if (this.processPermission) { - builder.append('p'); + if (this.deletePermission) { + builder.append('d'); } return builder.toString(); diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java index 0e2ce04aa3b2b..821a5045fa490 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java @@ -288,42 +288,42 @@ public void createTableSasSignatureValuesWithNullRequiredValue() { @Test public void tableSasPermissionToString() { - assertEquals("raup", new TableSasPermission() + assertEquals("raud", new TableSasPermission() .setReadPermission(true) .setAddPermission(true) .setUpdatePermission(true) - .setProcessPermission(true) + .setDeletePermission(true) .toString()); assertEquals("r", new TableSasPermission().setReadPermission(true).toString()); assertEquals("a", new TableSasPermission().setAddPermission(true).toString()); assertEquals("u", new TableSasPermission().setUpdatePermission(true).toString()); - assertEquals("p", new TableSasPermission().setProcessPermission(true).toString()); + assertEquals("d", new TableSasPermission().setDeletePermission(true).toString()); } @Test public void tableSasPermissionParse() { - TableSasPermission tableSasPermission = TableSasPermission.parse("raup"); + TableSasPermission tableSasPermission = TableSasPermission.parse("raud"); assertTrue(tableSasPermission.hasReadPermission()); assertTrue(tableSasPermission.hasAddPermission()); assertTrue(tableSasPermission.hasUpdatePermission()); - assertTrue(tableSasPermission.hasProcessPermission()); + assertTrue(tableSasPermission.hasDeletePermission()); - tableSasPermission = TableSasPermission.parse("urpa"); + tableSasPermission = TableSasPermission.parse("urda"); assertTrue(tableSasPermission.hasReadPermission()); assertTrue(tableSasPermission.hasAddPermission()); assertTrue(tableSasPermission.hasUpdatePermission()); - assertTrue(tableSasPermission.hasProcessPermission()); + assertTrue(tableSasPermission.hasDeletePermission()); assertTrue(TableSasPermission.parse("r").hasReadPermission()); assertTrue(TableSasPermission.parse("a").hasAddPermission()); assertTrue(TableSasPermission.parse("u").hasUpdatePermission()); - assertTrue(TableSasPermission.parse("p").hasProcessPermission()); + assertTrue(TableSasPermission.parse("d").hasDeletePermission()); } @Test public void tableSasPermissionParseIllegalString() { - assertThrows(IllegalArgumentException.class, () -> TableSasPermission.parse("rauq")); + assertThrows(IllegalArgumentException.class, () -> TableSasPermission.parse("raud")); } } diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableAsyncClientTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableAsyncClientTest.java index 518e6cbfbebf7..aea0ed4f55023 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableAsyncClientTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableAsyncClientTest.java @@ -12,14 +12,18 @@ import com.azure.core.http.rest.Response; import com.azure.core.test.TestBase; import com.azure.core.test.utils.TestResourceNamer; -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.TableTransactionAction; +import com.azure.data.tables.models.TableTransactionActionResponse; import com.azure.data.tables.models.TableTransactionActionType; import com.azure.data.tables.models.TableTransactionFailedException; import com.azure.data.tables.models.TableTransactionResult; +import com.azure.data.tables.sas.TableSasIpRange; +import com.azure.data.tables.sas.TableSasPermission; +import com.azure.data.tables.sas.TableSasProtocol; +import com.azure.data.tables.sas.TableSasSignatureValues; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -29,6 +33,7 @@ import java.time.Duration; import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -72,13 +77,16 @@ protected void beforeTest() { if (interceptorManager.isPlaybackMode()) { playbackClient = interceptorManager.getPlaybackClient(); + builder.httpClient(playbackClient); } else { builder.httpClient(HttpClient.createDefault()); if (!interceptorManager.isLiveMode()) { recordPolicy = interceptorManager.getRecordPolicy(); + builder.addPolicy(recordPolicy); } + builder.addPolicy(new RetryPolicy(new ExponentialBackoff(6, Duration.ofMillis(1500), Duration.ofSeconds(100)))); } @@ -884,4 +892,121 @@ void submitTransactionAsyncWithDifferentPartitionKeys() { && e.getMessage().contains("rowKey='" + rowKeyValue2)) .verify(); } + + @Test + @Tag("SAS") + public void generateSasTokenWithMinimumParameters() { + final OffsetDateTime expiryTime = OffsetDateTime.of(2021, 12, 12, 0, 0, 0, 0, ZoneOffset.UTC); + final TableSasPermission permissions = TableSasPermission.parse("r"); + final TableSasProtocol protocol = TableSasProtocol.HTTPS_ONLY; + + final TableSasSignatureValues sasSignatureValues = + new TableSasSignatureValues(expiryTime, permissions) + .setProtocol(protocol) + .setVersion(TableServiceVersion.V2019_02_02.getVersion()); + + final String sas = tableClient.generateSas(sasSignatureValues); + + assertTrue( + sas.startsWith( + "sv=2019-02-02" + + "&se=2021-12-12T00%3A00%3A00Z" + + "&tn=" + tableClient.getTableName() + + "&sp=r" + + "&spr=https" + + "&sig=" + ) + ); + } + + @Test + @Tag("SAS") + public void generateSasTokenWithAllParameters() { + final OffsetDateTime expiryTime = OffsetDateTime.of(2021, 12, 12, 0, 0, 0, 0, ZoneOffset.UTC); + final TableSasPermission permissions = TableSasPermission.parse("raud"); + final TableSasProtocol protocol = TableSasProtocol.HTTPS_HTTP; + + final OffsetDateTime startTime = OffsetDateTime.of(2015, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + final TableSasIpRange ipRange = TableSasIpRange.parse("a-b"); + final String startPartitionKey = "startPartitionKey"; + final String startRowKey = "startRowKey"; + final String endPartitionKey = "endPartitionKey"; + final String endRowKey = "endRowKey"; + + final TableSasSignatureValues sasSignatureValues = + new TableSasSignatureValues(expiryTime, permissions) + .setProtocol(protocol) + .setVersion(TableServiceVersion.V2019_02_02.getVersion()) + .setStartTime(startTime) + .setSasIpRange(ipRange) + .setStartPartitionKey(startPartitionKey) + .setStartRowKey(startRowKey) + .setEndPartitionKey(endPartitionKey) + .setEndRowKey(endRowKey); + + final String sas = tableClient.generateSas(sasSignatureValues); + + assertTrue( + sas.startsWith( + "sv=2019-02-02" + + "&st=2015-01-01T00%3A00%3A00Z" + + "&se=2021-12-12T00%3A00%3A00Z" + + "&tn=" + tableClient.getTableName() + + "&sp=raud" + + "&spk=startPartitionKey" + + "&srk=startRowKey" + + "&epk=endPartitionKey" + + "&erk=endRowKey" + + "&sip=a-b" + + "&spr=https%2Chttp" + + "&sig=" + ) + ); + } + + @Test + @Tag("SAS") + public void canUseSasTokenToCreateValidTableClient() { + final OffsetDateTime expiryTime = OffsetDateTime.of(2021, 12, 12, 0, 0, 0, 0, ZoneOffset.UTC); + final TableSasPermission permissions = TableSasPermission.parse("a"); + final TableSasProtocol protocol = TableSasProtocol.HTTPS_HTTP; + + final TableSasSignatureValues sasSignatureValues = + new TableSasSignatureValues(expiryTime, permissions) + .setProtocol(protocol) + .setVersion(TableServiceVersion.V2019_02_02.getVersion()); + + final String sas = tableClient.generateSas(sasSignatureValues); + + final TableClientBuilder tableClientBuilder = new TableClientBuilder() + .httpLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS)) + .endpoint(tableClient.getTableEndpoint()) + .sasToken(sas) + .tableName(tableClient.getTableName()); + + if (interceptorManager.isPlaybackMode()) { + tableClientBuilder.httpClient(playbackClient); + } else { + tableClientBuilder.httpClient(HttpClient.createDefault()); + + if (!interceptorManager.isLiveMode()) { + tableClientBuilder.addPolicy(recordPolicy); + } + + tableClientBuilder.addPolicy(new RetryPolicy(new ExponentialBackoff(6, Duration.ofMillis(1500), + Duration.ofSeconds(100)))); + } + + // Create a new client authenticated with the SAS token. + final TableAsyncClient tableAsyncClient = tableClientBuilder.buildAsyncClient(); + final String partitionKeyValue = testResourceNamer.randomName("partitionKey", 20); + final String rowKeyValue = testResourceNamer.randomName("rowKey", 20); + final TableEntity entity = new TableEntity(partitionKeyValue, rowKeyValue); + final int expectedStatusCode = 204; + + StepVerifier.create(tableAsyncClient.createEntityWithResponse(entity)) + .assertNext(response -> assertEquals(expectedStatusCode, response.getStatusCode())) + .expectComplete() + .verify(); + } } diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java index 3f5c21779cc63..b1dc956dd6de7 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java @@ -7,10 +7,20 @@ import com.azure.core.http.policy.ExponentialBackoff; import com.azure.core.http.policy.HttpLogDetailLevel; import com.azure.core.http.policy.HttpLogOptions; +import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.http.policy.RetryPolicy; import com.azure.core.test.TestBase; import com.azure.data.tables.models.ListTablesOptions; +import com.azure.data.tables.models.TableEntity; import com.azure.data.tables.models.TableServiceException; +import com.azure.data.tables.sas.TableAccountSasPermission; +import com.azure.data.tables.sas.TableAccountSasResourceType; +import com.azure.data.tables.sas.TableAccountSasService; +import com.azure.data.tables.sas.TableAccountSasSignatureValues; +import com.azure.data.tables.sas.TableSasIpRange; +import com.azure.data.tables.sas.TableSasPermission; +import com.azure.data.tables.sas.TableSasProtocol; +import com.azure.data.tables.sas.TableSasSignatureValues; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -19,10 +29,13 @@ import reactor.test.StepVerifier; import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests methods for {@link TableServiceAsyncClient}. @@ -30,6 +43,8 @@ public class TableServiceAsyncClientTest extends TestBase { private static final Duration TIMEOUT = Duration.ofSeconds(100); private TableServiceAsyncClient serviceClient; + private HttpPipelinePolicy recordPolicy; + private HttpClient playbackClient; @BeforeAll static void beforeAll() { @@ -49,12 +64,18 @@ protected void beforeTest() { .httpLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS)); if (interceptorManager.isPlaybackMode()) { - builder.httpClient(interceptorManager.getPlaybackClient()); + playbackClient = interceptorManager.getPlaybackClient(); + + builder.httpClient(playbackClient); } else { builder.httpClient(HttpClient.createDefault()); + if (!interceptorManager.isLiveMode()) { - builder.addPolicy(interceptorManager.getRecordPolicy()); + recordPolicy = interceptorManager.getRecordPolicy(); + + builder.addPolicy(recordPolicy); } + builder.addPolicy(new RetryPolicy(new ExponentialBackoff(6, Duration.ofMillis(1500), Duration.ofSeconds(100)))); } @@ -279,4 +300,121 @@ void serviceGetTableClientAsync() { // Act & Assert TableAsyncClientTest.getEntityWithResponseAsyncImpl(tableClient, this.testResourceNamer); } + + @Test + @Tag("SAS") + public void generateAccountSasTokenWithMinimumParameters() { + final OffsetDateTime expiryTime = OffsetDateTime.of(2021, 12, 12, 0, 0, 0, 0, ZoneOffset.UTC); + final TableAccountSasPermission permissions = TableAccountSasPermission.parse("r"); + final TableAccountSasService services = new TableAccountSasService().setTableAccess(true); + final TableAccountSasResourceType resourceTypes = new TableAccountSasResourceType().setObject(true); + final TableSasProtocol protocol = TableSasProtocol.HTTPS_ONLY; + + final TableAccountSasSignatureValues sasSignatureValues = + new TableAccountSasSignatureValues(expiryTime, permissions, services, resourceTypes) + .setProtocol(protocol) + .setVersion(TableServiceVersion.V2019_02_02.getVersion()); + + final String sas = serviceClient.generateAccountSas(sasSignatureValues); + + assertTrue( + sas.startsWith( + "sv=2019-02-02" + + "&ss=t" + + "&srt=o" + + "&se=2021-12-12T00%3A00%3A00Z" + + "&sp=r" + + "&spr=https" + + "&sig=" + ) + ); + } + + @Test + @Tag("SAS") + public void generateAccountSasTokenWithAllParameters() { + final OffsetDateTime expiryTime = OffsetDateTime.of(2021, 12, 12, 0, 0, 0, 0, ZoneOffset.UTC); + final TableAccountSasPermission permissions = TableAccountSasPermission.parse("rdau"); + final TableAccountSasService services = new TableAccountSasService().setTableAccess(true); + final TableAccountSasResourceType resourceTypes = new TableAccountSasResourceType().setObject(true); + final TableSasProtocol protocol = TableSasProtocol.HTTPS_HTTP; + + final OffsetDateTime startTime = OffsetDateTime.of(2015, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); + final TableSasIpRange ipRange = TableSasIpRange.parse("a-b"); + + final TableAccountSasSignatureValues sasSignatureValues = + new TableAccountSasSignatureValues(expiryTime, permissions, services, resourceTypes) + .setProtocol(protocol) + .setVersion(TableServiceVersion.V2019_02_02.getVersion()) + .setStartTime(startTime) + .setSasIpRange(ipRange); + + final String sas = serviceClient.generateAccountSas(sasSignatureValues); + + assertTrue( + sas.startsWith( + "sv=2019-02-02" + + "&ss=t" + + "&srt=o" + + "&st=2015-01-01T00%3A00%3A00Z" + + "&se=2021-12-12T00%3A00%3A00Z" + + "&sp=rdau" + + "&sip=a-b" + + "&spr=https%2Chttp" + + "&sig=" + ) + ); + } + + @Test + @Tag("SAS") + public void canUseSasTokenToCreateValidTableClient() { + final OffsetDateTime expiryTime = OffsetDateTime.of(2021, 12, 12, 0, 0, 0, 0, ZoneOffset.UTC); + final TableAccountSasPermission permissions = TableAccountSasPermission.parse("a"); + final TableAccountSasService services = new TableAccountSasService().setTableAccess(true); + final TableAccountSasResourceType resourceTypes = new TableAccountSasResourceType().setObject(true); + final TableSasProtocol protocol = TableSasProtocol.HTTPS_ONLY; + + final TableAccountSasSignatureValues sasSignatureValues = + new TableAccountSasSignatureValues(expiryTime, permissions, services, resourceTypes) + .setProtocol(protocol) + .setVersion(TableServiceVersion.V2019_02_02.getVersion()); + + final String sas = serviceClient.generateAccountSas(sasSignatureValues); + final String tableName = testResourceNamer.randomName("test", 20); + + serviceClient.createTable(tableName).block(TIMEOUT); + + final TableClientBuilder tableClientBuilder = new TableClientBuilder() + .httpLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS)) + .endpoint(serviceClient.getServiceEndpoint()) + .sasToken(sas) + .tableName(tableName); + + if (interceptorManager.isPlaybackMode()) { + tableClientBuilder.httpClient(playbackClient); + } else { + tableClientBuilder.httpClient(HttpClient.createDefault()); + + if (!interceptorManager.isLiveMode()) { + tableClientBuilder.addPolicy(recordPolicy); + } + + tableClientBuilder.addPolicy(new RetryPolicy(new ExponentialBackoff(6, Duration.ofMillis(1500), + Duration.ofSeconds(100)))); + } + + // Create a new client authenticated with the SAS token. + final TableAsyncClient tableAsyncClient = tableClientBuilder.buildAsyncClient(); + final String partitionKeyValue = testResourceNamer.randomName("partitionKey", 20); + final String rowKeyValue = testResourceNamer.randomName("rowKey", 20); + final TableEntity entity = new TableEntity(partitionKeyValue, rowKeyValue); + final int expectedStatusCode = 204; + + //Act & Assert + StepVerifier.create(tableAsyncClient.createEntityWithResponse(entity)) + .assertNext(response -> assertEquals(expectedStatusCode, response.getStatusCode())) + .expectComplete() + .verify(); + } } diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.canUseSasTokenToCreateValidTableClient.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.canUseSasTokenToCreateValidTableClient.json new file mode 100644 index 0000000000000..1cd2ed5e5dc02 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.canUseSasTokenToCreateValidTableClient.json @@ -0,0 +1,55 @@ +{ + "networkCallRecords" : [ { + "Method" : "POST", + "Uri" : "https://REDACTED.table.core.windows.net/Tables", + "Headers" : { + "x-ms-version" : "2019-02-02", + "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", + "x-ms-client-request-id" : "9bf0ce98-3161-4947-8f85-7b00cbb399f9", + "Content-Type" : "application/json;odata=nometadata" + }, + "Response" : { + "content-length" : "0", + "x-ms-version" : "2019-02-02", + "Server" : "Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0", + "X-Content-Type-Options" : "nosniff", + "retry-after" : "0", + "StatusCode" : "204", + "Date" : "Fri, 04 Jun 2021 19:40:13 GMT", + "Cache-Control" : "no-cache", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename164599cd')", + "x-ms-request-id" : "7b4422de-1002-0005-2a79-592a1e000000", + "x-ms-client-request-id" : "9bf0ce98-3161-4947-8f85-7b00cbb399f9", + "Preference-Applied" : "return-no-content", + "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename164599cd')" + }, + "Exception" : null + }, { + "Method" : "POST", + "Uri" : "https://REDACTED.table.core.windows.net/tablename164599cd/tablename164599cd?sv=2019-02-02&se=2021-12-12T00%3A00%3A00Z&tn=tablename164599cd&sp=raud&spr=https%2Chttp&sig=REDACTED", + "Headers" : { + "x-ms-version" : "2019-02-02", + "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", + "x-ms-client-request-id" : "e6dde93d-c422-4f0f-ab5d-455f56104325", + "Content-Type" : "application/json;odata=nometadata" + }, + "Response" : { + "content-length" : "0", + "x-ms-version" : "2019-02-02", + "Server" : "Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0", + "X-Content-Type-Options" : "nosniff", + "retry-after" : "0", + "StatusCode" : "204", + "Date" : "Fri, 04 Jun 2021 19:40:13 GMT", + "Cache-Control" : "no-cache", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/tablename164599cd(PartitionKey='partitionkey379910',RowKey='rowkey8067455c9')", + "eTag" : "W/datetime'2021-06-04T19%3A40%3A14.7488934Z'", + "x-ms-request-id" : "7b442337-1002-0005-7f79-592a1e000000", + "x-ms-client-request-id" : "e6dde93d-c422-4f0f-ab5d-455f56104325", + "Preference-Applied" : "return-no-content", + "Location" : "https://tablesstoragetests.table.core.windows.net/tablename164599cd(PartitionKey='partitionkey379910',RowKey='rowkey8067455c9')" + }, + "Exception" : null + } ], + "variables" : [ "tablename164599cd", "partitionkey379910", "rowkey8067455c9" ] +} \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithAllParameters.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithAllParameters.json new file mode 100644 index 0000000000000..9486537d7672d --- /dev/null +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithAllParameters.json @@ -0,0 +1,29 @@ +{ + "networkCallRecords" : [ { + "Method" : "POST", + "Uri" : "https://REDACTED.table.core.windows.net/Tables", + "Headers" : { + "x-ms-version" : "2019-02-02", + "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", + "x-ms-client-request-id" : "71d58db3-4a24-48e2-a2e6-fe04c0e3a890", + "Content-Type" : "application/json;odata=nometadata" + }, + "Response" : { + "content-length" : "0", + "x-ms-version" : "2019-02-02", + "Server" : "Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0", + "X-Content-Type-Options" : "nosniff", + "retry-after" : "0", + "StatusCode" : "204", + "Date" : "Fri, 04 Jun 2021 19:18:24 GMT", + "Cache-Control" : "no-cache", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename66203de7')", + "x-ms-request-id" : "6bbed3e4-f002-004b-6876-59effb000000", + "x-ms-client-request-id" : "71d58db3-4a24-48e2-a2e6-fe04c0e3a890", + "Preference-Applied" : "return-no-content", + "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename66203de7')" + }, + "Exception" : null + } ], + "variables" : [ "tablename66203de7" ] +} \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithMinimumParameters.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithMinimumParameters.json new file mode 100644 index 0000000000000..e32bb75e8937b --- /dev/null +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithMinimumParameters.json @@ -0,0 +1,29 @@ +{ + "networkCallRecords" : [ { + "Method" : "POST", + "Uri" : "https://REDACTED.table.core.windows.net/Tables", + "Headers" : { + "x-ms-version" : "2019-02-02", + "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", + "x-ms-client-request-id" : "0d393732-0e27-44a2-b9b9-870c9fd17645", + "Content-Type" : "application/json;odata=nometadata" + }, + "Response" : { + "content-length" : "0", + "x-ms-version" : "2019-02-02", + "Server" : "Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0", + "X-Content-Type-Options" : "nosniff", + "retry-after" : "0", + "StatusCode" : "204", + "Date" : "Fri, 04 Jun 2021 19:18:24 GMT", + "Cache-Control" : "no-cache", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename099126c0')", + "x-ms-request-id" : "0c42558a-a002-003e-1376-596840000000", + "x-ms-client-request-id" : "0d393732-0e27-44a2-b9b9-870c9fd17645", + "Preference-Applied" : "return-no-content", + "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename099126c0')" + }, + "Exception" : null + } ], + "variables" : [ "tablename099126c0" ] +} \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.canUseSasTokenToCreateValidTableClient.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.canUseSasTokenToCreateValidTableClient.json new file mode 100644 index 0000000000000..bbc723db1ad48 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.canUseSasTokenToCreateValidTableClient.json @@ -0,0 +1,55 @@ +{ + "networkCallRecords" : [ { + "Method" : "POST", + "Uri" : "https://REDACTED.table.core.windows.net/Tables", + "Headers" : { + "x-ms-version" : "2019-02-02", + "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", + "x-ms-client-request-id" : "168cc95e-3ffb-4306-8fde-6aa668fc933d", + "Content-Type" : "application/json;odata=nometadata" + }, + "Response" : { + "content-length" : "0", + "x-ms-version" : "2019-02-02", + "Server" : "Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0", + "X-Content-Type-Options" : "nosniff", + "retry-after" : "0", + "StatusCode" : "204", + "Date" : "Fri, 04 Jun 2021 19:58:51 GMT", + "Cache-Control" : "no-cache", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('test051962b0cd')", + "x-ms-request-id" : "d59ef53f-f002-000f-317c-593397000000", + "x-ms-client-request-id" : "168cc95e-3ffb-4306-8fde-6aa668fc933d", + "Preference-Applied" : "return-no-content", + "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('test051962b0cd')" + }, + "Exception" : null + }, { + "Method" : "POST", + "Uri" : "https://REDACTED.table.core.windows.net/test051962b0cd?sv=2019-02-02&ss=t&srt=o&se=2021-12-12T00%3A00%3A00Z&sp=a&spr=https&sig=REDACTED", + "Headers" : { + "x-ms-version" : "2019-02-02", + "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", + "x-ms-client-request-id" : "799e3a0f-2368-4fdf-acd2-be02d6d43f15", + "Content-Type" : "application/json;odata=nometadata" + }, + "Response" : { + "content-length" : "0", + "x-ms-version" : "2019-02-02", + "Server" : "Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0", + "X-Content-Type-Options" : "nosniff", + "retry-after" : "0", + "StatusCode" : "204", + "Date" : "Fri, 04 Jun 2021 19:58:51 GMT", + "Cache-Control" : "no-cache", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/test051962b0cd(PartitionKey='partitionkey98855a',RowKey='rowkey62462a6ad')", + "eTag" : "W/datetime'2021-06-04T19%3A58%3A52.3788942Z'", + "x-ms-request-id" : "d59ef562-f002-000f-537c-593397000000", + "x-ms-client-request-id" : "799e3a0f-2368-4fdf-acd2-be02d6d43f15", + "Preference-Applied" : "return-no-content", + "Location" : "https://tablesstoragetests.table.core.windows.net/test051962b0cd(PartitionKey='partitionkey98855a',RowKey='rowkey62462a6ad')" + }, + "Exception" : null + } ], + "variables" : [ "test051962b0cd", "partitionkey98855a", "rowkey62462a6ad" ] +} \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.generateAccountSasTokenWithAllParameters.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.generateAccountSasTokenWithAllParameters.json new file mode 100644 index 0000000000000..ba5f37f8f8555 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.generateAccountSasTokenWithAllParameters.json @@ -0,0 +1,4 @@ +{ + "networkCallRecords" : [ ], + "variables" : [ ] +} \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.generateAccountSasTokenWithMinimumParameters.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.generateAccountSasTokenWithMinimumParameters.json new file mode 100644 index 0000000000000..ba5f37f8f8555 --- /dev/null +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.generateAccountSasTokenWithMinimumParameters.json @@ -0,0 +1,4 @@ +{ + "networkCallRecords" : [ ], + "variables" : [ ] +} \ No newline at end of file From 91786bb42827f886b07bfc8b0f3d14b31f55b722 Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 4 Jun 2021 15:02:40 -0500 Subject: [PATCH 22/25] Updated CHANGELOG. --- sdk/tables/azure-data-tables/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/tables/azure-data-tables/CHANGELOG.md b/sdk/tables/azure-data-tables/CHANGELOG.md index efc7ee0e3f981..f1815ce615e61 100644 --- a/sdk/tables/azure-data-tables/CHANGELOG.md +++ b/sdk/tables/azure-data-tables/CHANGELOG.md @@ -5,11 +5,12 @@ ### 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. +- Added support for generating SAS tokens at the Account and Table Service level in all clients. - Added the following methods to `TableClient`, `TableAsyncClient`: - `listAccessPolicies()` - `setAccessPolicies()` - `setAccessPoliciesWithResponse()` + - `generateSasToken()` - Added the following methods to `TableServiceClient`, `TableServiceAsyncClient`: - `getProperties()` - `getPropertiesWithResponse()` @@ -17,6 +18,7 @@ - `setPropertiesWithResponse()` - `getStatistics()` - `getStatisticsWithResponse()` + - `generateAccountSasToken()` ### Breaking Changes From 44d20a2e56d5bff86842a2e3d6cc6a872964d3eb Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 4 Jun 2021 15:27:34 -0500 Subject: [PATCH 23/25] Fixed test and CheckStyle issues. --- .../com/azure/data/tables/SasModelsTest.java | 2 +- .../tables/TableServiceAsyncClientTest.java | 2 -- ...anUseSasTokenToCreateValidTableClient.json | 30 +++++++++---------- ...est.generateSasTokenWithAllParameters.json | 14 ++++----- ...generateSasTokenWithMinimumParameters.json | 14 ++++----- ...anUseSasTokenToCreateValidTableClient.json | 30 +++++++++---------- 6 files changed, 45 insertions(+), 47 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java index 821a5045fa490..8be6348d27e22 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/SasModelsTest.java @@ -324,6 +324,6 @@ public void tableSasPermissionParse() { @Test public void tableSasPermissionParseIllegalString() { - assertThrows(IllegalArgumentException.class, () -> TableSasPermission.parse("raud")); + assertThrows(IllegalArgumentException.class, () -> TableSasPermission.parse("raup")); } } diff --git a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java index b1dc956dd6de7..b94b25f1f855a 100644 --- a/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java +++ b/sdk/tables/azure-data-tables/src/test/java/com/azure/data/tables/TableServiceAsyncClientTest.java @@ -18,9 +18,7 @@ import com.azure.data.tables.sas.TableAccountSasService; import com.azure.data.tables.sas.TableAccountSasSignatureValues; import com.azure.data.tables.sas.TableSasIpRange; -import com.azure.data.tables.sas.TableSasPermission; import com.azure.data.tables.sas.TableSasProtocol; -import com.azure.data.tables.sas.TableSasSignatureValues; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.canUseSasTokenToCreateValidTableClient.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.canUseSasTokenToCreateValidTableClient.json index 1cd2ed5e5dc02..f4a1368377bec 100644 --- a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.canUseSasTokenToCreateValidTableClient.json +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.canUseSasTokenToCreateValidTableClient.json @@ -5,7 +5,7 @@ "Headers" : { "x-ms-version" : "2019-02-02", "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", - "x-ms-client-request-id" : "9bf0ce98-3161-4947-8f85-7b00cbb399f9", + "x-ms-client-request-id" : "e56a3194-48cd-45f2-ace7-b1dfe471f364", "Content-Type" : "application/json;odata=nometadata" }, "Response" : { @@ -15,22 +15,22 @@ "X-Content-Type-Options" : "nosniff", "retry-after" : "0", "StatusCode" : "204", - "Date" : "Fri, 04 Jun 2021 19:40:13 GMT", + "Date" : "Fri, 04 Jun 2021 20:21:58 GMT", "Cache-Control" : "no-cache", - "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename164599cd')", - "x-ms-request-id" : "7b4422de-1002-0005-2a79-592a1e000000", - "x-ms-client-request-id" : "9bf0ce98-3161-4947-8f85-7b00cbb399f9", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename40793703')", + "x-ms-request-id" : "01f46ed0-a002-0035-257f-597034000000", + "x-ms-client-request-id" : "e56a3194-48cd-45f2-ace7-b1dfe471f364", "Preference-Applied" : "return-no-content", - "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename164599cd')" + "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename40793703')" }, "Exception" : null }, { "Method" : "POST", - "Uri" : "https://REDACTED.table.core.windows.net/tablename164599cd/tablename164599cd?sv=2019-02-02&se=2021-12-12T00%3A00%3A00Z&tn=tablename164599cd&sp=raud&spr=https%2Chttp&sig=REDACTED", + "Uri" : "https://REDACTED.table.core.windows.net/tablename40793703/tablename40793703?sv=2019-02-02&se=2021-12-12T00%3A00%3A00Z&tn=tablename40793703&sp=a&spr=https%2Chttp&sig=REDACTED", "Headers" : { "x-ms-version" : "2019-02-02", "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", - "x-ms-client-request-id" : "e6dde93d-c422-4f0f-ab5d-455f56104325", + "x-ms-client-request-id" : "587dac9f-2e50-47b0-9554-cc449d2b94ab", "Content-Type" : "application/json;odata=nometadata" }, "Response" : { @@ -40,16 +40,16 @@ "X-Content-Type-Options" : "nosniff", "retry-after" : "0", "StatusCode" : "204", - "Date" : "Fri, 04 Jun 2021 19:40:13 GMT", + "Date" : "Fri, 04 Jun 2021 20:21:58 GMT", "Cache-Control" : "no-cache", - "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/tablename164599cd(PartitionKey='partitionkey379910',RowKey='rowkey8067455c9')", - "eTag" : "W/datetime'2021-06-04T19%3A40%3A14.7488934Z'", - "x-ms-request-id" : "7b442337-1002-0005-7f79-592a1e000000", - "x-ms-client-request-id" : "e6dde93d-c422-4f0f-ab5d-455f56104325", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/tablename40793703(PartitionKey='partitionkey915773',RowKey='rowkey25200da12')", + "eTag" : "W/datetime'2021-06-04T20%3A21%3A58.6283474Z'", + "x-ms-request-id" : "01f46efe-a002-0035-4f7f-597034000000", + "x-ms-client-request-id" : "587dac9f-2e50-47b0-9554-cc449d2b94ab", "Preference-Applied" : "return-no-content", - "Location" : "https://tablesstoragetests.table.core.windows.net/tablename164599cd(PartitionKey='partitionkey379910',RowKey='rowkey8067455c9')" + "Location" : "https://tablesstoragetests.table.core.windows.net/tablename40793703(PartitionKey='partitionkey915773',RowKey='rowkey25200da12')" }, "Exception" : null } ], - "variables" : [ "tablename164599cd", "partitionkey379910", "rowkey8067455c9" ] + "variables" : [ "tablename40793703", "partitionkey915773", "rowkey25200da12" ] } \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithAllParameters.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithAllParameters.json index 9486537d7672d..c84249d7bdd65 100644 --- a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithAllParameters.json +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithAllParameters.json @@ -5,7 +5,7 @@ "Headers" : { "x-ms-version" : "2019-02-02", "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", - "x-ms-client-request-id" : "71d58db3-4a24-48e2-a2e6-fe04c0e3a890", + "x-ms-client-request-id" : "93a2a3c9-4159-40a8-9247-748b1f0b66f4", "Content-Type" : "application/json;odata=nometadata" }, "Response" : { @@ -15,15 +15,15 @@ "X-Content-Type-Options" : "nosniff", "retry-after" : "0", "StatusCode" : "204", - "Date" : "Fri, 04 Jun 2021 19:18:24 GMT", + "Date" : "Fri, 04 Jun 2021 20:21:57 GMT", "Cache-Control" : "no-cache", - "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename66203de7')", - "x-ms-request-id" : "6bbed3e4-f002-004b-6876-59effb000000", - "x-ms-client-request-id" : "71d58db3-4a24-48e2-a2e6-fe04c0e3a890", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename17721fc9')", + "x-ms-request-id" : "7b55d062-1002-0005-137f-592a1e000000", + "x-ms-client-request-id" : "93a2a3c9-4159-40a8-9247-748b1f0b66f4", "Preference-Applied" : "return-no-content", - "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename66203de7')" + "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename17721fc9')" }, "Exception" : null } ], - "variables" : [ "tablename66203de7" ] + "variables" : [ "tablename17721fc9" ] } \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithMinimumParameters.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithMinimumParameters.json index e32bb75e8937b..85584ab2fdb18 100644 --- a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithMinimumParameters.json +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableAsyncClientTest.generateSasTokenWithMinimumParameters.json @@ -5,7 +5,7 @@ "Headers" : { "x-ms-version" : "2019-02-02", "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", - "x-ms-client-request-id" : "0d393732-0e27-44a2-b9b9-870c9fd17645", + "x-ms-client-request-id" : "943ad224-303a-4763-8e83-a818550503c7", "Content-Type" : "application/json;odata=nometadata" }, "Response" : { @@ -15,15 +15,15 @@ "X-Content-Type-Options" : "nosniff", "retry-after" : "0", "StatusCode" : "204", - "Date" : "Fri, 04 Jun 2021 19:18:24 GMT", + "Date" : "Fri, 04 Jun 2021 20:21:56 GMT", "Cache-Control" : "no-cache", - "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename099126c0')", - "x-ms-request-id" : "0c42558a-a002-003e-1376-596840000000", - "x-ms-client-request-id" : "0d393732-0e27-44a2-b9b9-870c9fd17645", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename69053ff7')", + "x-ms-request-id" : "1ab91518-c002-0025-707f-5946d2000000", + "x-ms-client-request-id" : "943ad224-303a-4763-8e83-a818550503c7", "Preference-Applied" : "return-no-content", - "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename099126c0')" + "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('tablename69053ff7')" }, "Exception" : null } ], - "variables" : [ "tablename099126c0" ] + "variables" : [ "tablename69053ff7" ] } \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.canUseSasTokenToCreateValidTableClient.json b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.canUseSasTokenToCreateValidTableClient.json index bbc723db1ad48..7f30fd87b782b 100644 --- a/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.canUseSasTokenToCreateValidTableClient.json +++ b/sdk/tables/azure-data-tables/src/test/resources/session-records/TableServiceAsyncClientTest.canUseSasTokenToCreateValidTableClient.json @@ -5,7 +5,7 @@ "Headers" : { "x-ms-version" : "2019-02-02", "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", - "x-ms-client-request-id" : "168cc95e-3ffb-4306-8fde-6aa668fc933d", + "x-ms-client-request-id" : "ebc98fd2-eee5-4da3-a077-62459e7024b6", "Content-Type" : "application/json;odata=nometadata" }, "Response" : { @@ -15,22 +15,22 @@ "X-Content-Type-Options" : "nosniff", "retry-after" : "0", "StatusCode" : "204", - "Date" : "Fri, 04 Jun 2021 19:58:51 GMT", + "Date" : "Fri, 04 Jun 2021 20:20:48 GMT", "Cache-Control" : "no-cache", - "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('test051962b0cd')", - "x-ms-request-id" : "d59ef53f-f002-000f-317c-593397000000", - "x-ms-client-request-id" : "168cc95e-3ffb-4306-8fde-6aa668fc933d", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/Tables('test859634c577')", + "x-ms-request-id" : "0ea574e2-e002-001b-4b7f-59f0f3000000", + "x-ms-client-request-id" : "ebc98fd2-eee5-4da3-a077-62459e7024b6", "Preference-Applied" : "return-no-content", - "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('test051962b0cd')" + "Location" : "https://tablesstoragetests.table.core.windows.net/Tables('test859634c577')" }, "Exception" : null }, { "Method" : "POST", - "Uri" : "https://REDACTED.table.core.windows.net/test051962b0cd?sv=2019-02-02&ss=t&srt=o&se=2021-12-12T00%3A00%3A00Z&sp=a&spr=https&sig=REDACTED", + "Uri" : "https://REDACTED.table.core.windows.net/test859634c577?sv=2019-02-02&ss=t&srt=o&se=2021-12-12T00%3A00%3A00Z&sp=a&spr=https&sig=REDACTED", "Headers" : { "x-ms-version" : "2019-02-02", "User-Agent" : "azsdk-java-azure-data-tables/12.0.0-beta.8 (11.0.6; Windows 10; 10.0)", - "x-ms-client-request-id" : "799e3a0f-2368-4fdf-acd2-be02d6d43f15", + "x-ms-client-request-id" : "b2b12824-3264-46b5-a8ac-b546ea263bc6", "Content-Type" : "application/json;odata=nometadata" }, "Response" : { @@ -40,16 +40,16 @@ "X-Content-Type-Options" : "nosniff", "retry-after" : "0", "StatusCode" : "204", - "Date" : "Fri, 04 Jun 2021 19:58:51 GMT", + "Date" : "Fri, 04 Jun 2021 20:20:49 GMT", "Cache-Control" : "no-cache", - "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/test051962b0cd(PartitionKey='partitionkey98855a',RowKey='rowkey62462a6ad')", - "eTag" : "W/datetime'2021-06-04T19%3A58%3A52.3788942Z'", - "x-ms-request-id" : "d59ef562-f002-000f-537c-593397000000", - "x-ms-client-request-id" : "799e3a0f-2368-4fdf-acd2-be02d6d43f15", + "DataServiceId" : "https://tablesstoragetests.table.core.windows.net/test859634c577(PartitionKey='partitionkey55134c',RowKey='rowkey71408f997')", + "eTag" : "W/datetime'2021-06-04T20%3A20%3A49.7021587Z'", + "x-ms-request-id" : "0ea5751a-e002-001b-017f-59f0f3000000", + "x-ms-client-request-id" : "b2b12824-3264-46b5-a8ac-b546ea263bc6", "Preference-Applied" : "return-no-content", - "Location" : "https://tablesstoragetests.table.core.windows.net/test051962b0cd(PartitionKey='partitionkey98855a',RowKey='rowkey62462a6ad')" + "Location" : "https://tablesstoragetests.table.core.windows.net/test859634c577(PartitionKey='partitionkey55134c',RowKey='rowkey71408f997')" }, "Exception" : null } ], - "variables" : [ "test051962b0cd", "partitionkey98855a", "rowkey62462a6ad" ] + "variables" : [ "test859634c577", "partitionkey55134c", "rowkey71408f997" ] } \ No newline at end of file From 096a4928ea3f7e9c83c06446aa8c08a0ba6cac7a Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 4 Jun 2021 15:55:33 -0500 Subject: [PATCH 24/25] Added @Immutable and @Fluent annotations where appropriate. Made more models and classes in the sas package final. --- .../src/main/java/com/azure/data/tables/BuilderHelper.java | 4 ++-- .../data/tables/models/TableTransactionFailedException.java | 2 +- .../com/azure/data/tables/models/TableTransactionResult.java | 2 +- .../main/java/com/azure/data/tables/sas/TableSasIpRange.java | 4 ++++ .../com/azure/data/tables/sas/TableSasSignatureValues.java | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java index 7d4b9260192e8..788e5d1fb9fa6 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/BuilderHelper.java @@ -59,8 +59,8 @@ static HttpPipeline buildPipeline( if (endpoint == null) { throw logger.logExceptionAsError( - new IllegalStateException("An 'endpoint' is required to create a client. Use a builder's 'endpoint()'" - + " or 'connectionString()' methods to set this value.")); + new IllegalStateException("An 'endpoint' is required to create a client. Use builders' 'endpoint()' or" + + " 'connectionString()' methods to set this value.")); } else if (endpoint.contains(COSMOS_ENDPOINT_SUFFIX)) { policies.add(new CosmosPatchTransformPolicy()); } diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionFailedException.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionFailedException.java index 06d45cbd1d94e..a881e9fc335e6 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionFailedException.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionFailedException.java @@ -13,7 +13,7 @@ /** * Exception thrown for an invalid response on a transactional operation with {@link TableServiceError} information. */ -public class TableTransactionFailedException extends TableServiceException { +public final class TableTransactionFailedException extends TableServiceException { private final Integer failedTransactionActionIndex; /** diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionResult.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionResult.java index ee5aeb2c1f6c1..2b76cdbb16d49 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionResult.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionResult.java @@ -16,7 +16,7 @@ * {@link TableClient#submitTransactionWithResponse(List, Duration, Context)}, * {@link TableAsyncClient#submitTransaction(List)} or {@link TableAsyncClient#submitTransactionWithResponse(List)}. */ -public class TableTransactionResult { +public final class TableTransactionResult { private final List transactionActionResponses; private final Map lookupMap; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java index 2c11f2a7fc70e..ee8a7c62620b8 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java @@ -6,6 +6,10 @@ * This type specifies a continuous range of IP addresses. It is used to limit permissions on SAS tokens. Null may be * set if it is not desired to confine the sas permissions to an IP range. */ + +import com.azure.core.annotation.Fluent; + +@Fluent public final class TableSasIpRange { private String ipMin; private String ipMax; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java index 0ea5851672a45..4d736c67a0800 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasSignatureValues.java @@ -13,7 +13,7 @@ * of the SAS which can then be applied to a new client using the {@code sasToken(String)} method on the desired * client builder. * - * @see Storage SAS overview + * @see Storage SAS overview * @see Constructing a Service SAS */ @Fluent From 576b10603a1197444404c4bb3de2681ef0a6b61a Mon Sep 17 00:00:00 2001 From: Victor Colin Amador Date: Fri, 4 Jun 2021 16:26:56 -0500 Subject: [PATCH 25/25] Added more @Immutable annotations. --- .../com/azure/data/tables/models/TableServiceException.java | 2 ++ .../data/tables/models/TableTransactionFailedException.java | 2 ++ .../com/azure/data/tables/models/TableTransactionResult.java | 2 ++ .../main/java/com/azure/data/tables/sas/TableSasIpRange.java | 5 ++--- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableServiceException.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableServiceException.java index f8e7e6e3ae66d..7b2c565ac207f 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableServiceException.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableServiceException.java @@ -2,12 +2,14 @@ // Licensed under the MIT License. package com.azure.data.tables.models; +import com.azure.core.annotation.Immutable; import com.azure.core.exception.HttpResponseException; import com.azure.core.http.HttpResponse; /** * Exception thrown for an invalid response with {@link TableServiceError} information. */ +@Immutable public class TableServiceException extends HttpResponseException { /** * Initializes a new instance of the {@link TableServiceException} class. diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionFailedException.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionFailedException.java index a881e9fc335e6..6d6e8f3d2df77 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionFailedException.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionFailedException.java @@ -2,6 +2,7 @@ // Licensed under the MIT License. package com.azure.data.tables.models; +import com.azure.core.annotation.Immutable; import com.azure.core.http.HttpResponse; import com.azure.core.util.Context; import com.azure.data.tables.TableAsyncClient; @@ -13,6 +14,7 @@ /** * Exception thrown for an invalid response on a transactional operation with {@link TableServiceError} information. */ +@Immutable public final class TableTransactionFailedException extends TableServiceException { private final Integer failedTransactionActionIndex; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionResult.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionResult.java index 2b76cdbb16d49..ed0225d0833e7 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionResult.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/models/TableTransactionResult.java @@ -2,6 +2,7 @@ // Licensed under the MIT License. package com.azure.data.tables.models; +import com.azure.core.annotation.Immutable; import com.azure.core.util.Context; import com.azure.data.tables.TableAsyncClient; import com.azure.data.tables.TableClient; @@ -16,6 +17,7 @@ * {@link TableClient#submitTransactionWithResponse(List, Duration, Context)}, * {@link TableAsyncClient#submitTransaction(List)} or {@link TableAsyncClient#submitTransactionWithResponse(List)}. */ +@Immutable public final class TableTransactionResult { private final List transactionActionResponses; private final Map lookupMap; diff --git a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java index ee8a7c62620b8..d60ee0ed7f26b 100644 --- a/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java +++ b/sdk/tables/azure-data-tables/src/main/java/com/azure/data/tables/sas/TableSasIpRange.java @@ -2,13 +2,12 @@ // Licensed under the MIT License. package com.azure.data.tables.sas; +import com.azure.core.annotation.Fluent; + /** * This type specifies a continuous range of IP addresses. It is used to limit permissions on SAS tokens. Null may be * set if it is not desired to confine the sas permissions to an IP range. */ - -import com.azure.core.annotation.Fluent; - @Fluent public final class TableSasIpRange { private String ipMin;