-
Notifications
You must be signed in to change notification settings - Fork 9.2k
HADOOP-19406. ABFS: [FNSOverBlob] Support User Delegation SAS for FNS Blob #7523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
b124f05
dab40ab
317c523
48b46cf
e1e66c8
021abb2
a01beb7
6651d38
e95e230
82ef314
87e0664
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -120,6 +120,7 @@ | |
| import static org.apache.hadoop.fs.Options.OpenFileOptions.FS_OPTION_OPENFILE_STANDARD_OPTIONS; | ||
| import static org.apache.hadoop.fs.azurebfs.AbfsStatistic.*; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.CPK_IN_NON_HNS_ACCOUNT_ERROR_MESSAGE; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.AbfsServiceType.DFS; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.DATA_BLOCKS_BUFFER; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_IS_HNS_ENABLED; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_BLOCK_UPLOAD_ACTIVE_BLOCKS; | ||
|
|
@@ -250,6 +251,7 @@ public void initialize(URI uri, Configuration configuration) | |
| try { | ||
| if (abfsConfiguration.getAuthType(abfsConfiguration.getAccountName()) == AuthType.SAS && // Auth type is SAS | ||
| !tryGetIsNamespaceEnabled(new TracingContext(initFSTracingContext)) && // Account is FNS | ||
| abfsConfiguration.getFsConfiguredServiceType() == DFS && // Service type is DFS | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the constant for DFS
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is using the AbfsServiceType.DFS constant here |
||
| !abfsConfiguration.isFixedSASTokenProviderConfigured()) { // Fixed SAS Token Provider is not configured | ||
| throw new InvalidConfigurationValueException(FS_AZURE_SAS_FIXED_TOKEN, UNAUTHORIZED_SAS); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -364,7 +364,7 @@ public AbfsRestOperation listPath(final String relativePath, final boolean recur | |
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_DELIMITER, FORWARD_SLASH); | ||
| } | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_MAX_RESULTS, String.valueOf(listMaxResults)); | ||
| appendSASTokenToQuery(relativePath, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder); | ||
| appendSASTokenToQuery(relativePath, SASTokenProvider.LIST_OPERATION_BLOB, abfsUriQueryBuilder); | ||
anujmodi2021 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| final URL url = createRequestUrl(abfsUriQueryBuilder.toString()); | ||
| final AbfsRestOperation op = getAbfsRestOperation( | ||
|
|
@@ -539,11 +539,14 @@ public AbfsRestOperation createPathRestOp(final String path, | |
| final ContextEncryptionAdapter contextEncryptionAdapter, | ||
| final TracingContext tracingContext) throws AzureBlobFileSystemException { | ||
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| if (isFile) { | ||
| addEncryptionKeyRequestHeaders(path, requestHeaders, true, | ||
| contextEncryptionAdapter, tracingContext); | ||
| appendSASTokenToQuery(path, SASTokenProvider.CREATE_FILE_OPERATION, abfsUriQueryBuilder); | ||
| } else { | ||
| requestHeaders.add(new AbfsHttpHeader(X_MS_META_HDI_ISFOLDER, TRUE)); | ||
| appendSASTokenToQuery(path, SASTokenProvider.CREATE_DIRECTORY_OPERATION, abfsUriQueryBuilder); | ||
| } | ||
| requestHeaders.add(new AbfsHttpHeader(CONTENT_LENGTH, ZERO)); | ||
| if (isAppendBlob) { | ||
|
|
@@ -558,9 +561,6 @@ public AbfsRestOperation createPathRestOp(final String path, | |
| requestHeaders.add(new AbfsHttpHeader(HttpHeaderConfigurations.IF_MATCH, eTag)); | ||
| } | ||
|
|
||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
| final AbfsRestOperation op = getAbfsRestOperation( | ||
| AbfsRestOperationType.PutBlob, | ||
|
|
@@ -682,7 +682,7 @@ public AbfsRestOperation acquireLease(final String path, | |
|
|
||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, LEASE); | ||
| appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder); | ||
| appendSASTokenToQuery(path, SASTokenProvider.LEASE_BLOB_OPERATION, abfsUriQueryBuilder); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
| final AbfsRestOperation op = getAbfsRestOperation( | ||
|
|
@@ -710,7 +710,7 @@ public AbfsRestOperation renewLease(final String path, final String leaseId, | |
|
|
||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, LEASE); | ||
| appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder); | ||
| appendSASTokenToQuery(path, SASTokenProvider.LEASE_BLOB_OPERATION, abfsUriQueryBuilder); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
| final AbfsRestOperation op = getAbfsRestOperation( | ||
|
|
@@ -738,7 +738,7 @@ public AbfsRestOperation releaseLease(final String path, final String leaseId, | |
|
|
||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, LEASE); | ||
| appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder); | ||
| appendSASTokenToQuery(path, SASTokenProvider.LEASE_BLOB_OPERATION, abfsUriQueryBuilder); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
| final AbfsRestOperation op = getAbfsRestOperation( | ||
|
|
@@ -765,7 +765,7 @@ public AbfsRestOperation breakLease(final String path, | |
|
|
||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, LEASE); | ||
| appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder); | ||
| appendSASTokenToQuery(path, SASTokenProvider.LEASE_BLOB_OPERATION, abfsUriQueryBuilder); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
| final AbfsRestOperation op = getAbfsRestOperation( | ||
|
|
@@ -813,12 +813,15 @@ destination, sourceEtag, isAtomicRenameKey(source), tracingContext | |
| if (blobRenameHandler.execute()) { | ||
| final AbfsUriQueryBuilder abfsUriQueryBuilder | ||
| = createDefaultUriQueryBuilder(); | ||
| appendSASTokenToQuery(source, SASTokenProvider.RENAME_SOURCE_OPERATION, | ||
| abfsUriQueryBuilder); | ||
| final URL url = createRequestUrl(destination, | ||
| abfsUriQueryBuilder.toString()); | ||
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
| final AbfsRestOperation successOp = getSuccessOp( | ||
| AbfsRestOperationType.RenamePath, HTTP_METHOD_PUT, | ||
| url, requestHeaders); | ||
| successOp.setMask(); | ||
|
||
| return new AbfsClientRenameResult(successOp, true, false); | ||
| } else { | ||
| throw new AbfsRestOperationException(HTTP_INTERNAL_ERROR, | ||
|
|
@@ -886,7 +889,7 @@ public AbfsRestOperation append(final String path, | |
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, BLOCK); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_BLOCKID, reqParams.getBlockId()); | ||
|
|
||
| String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, | ||
| String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.WRITE_OPERATION, | ||
| abfsUriQueryBuilder, cachedSasToken); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
|
|
@@ -959,7 +962,7 @@ public AbfsRestOperation appendBlock(final String path, | |
| } | ||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, APPEND_BLOCK); | ||
| String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder); | ||
| String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.WRITE_OPERATION, abfsUriQueryBuilder); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
| final AbfsRestOperation op = getAbfsRestOperation( | ||
|
|
@@ -1051,7 +1054,7 @@ public AbfsRestOperation flush(byte[] buffer, | |
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, BLOCKLIST); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_CLOSE, String.valueOf(isClose)); | ||
| String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, | ||
| String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.WRITE_OPERATION, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is using WRITE_OPERATION for the flush call a good idea? Should we create another operation type for this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have the same operation type for DFS flush call as well. We can keep it as it is I think |
||
| abfsUriQueryBuilder, cachedSasToken); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
|
|
@@ -1113,7 +1116,7 @@ public AbfsRestOperation setPathProperties(final String path, | |
|
|
||
| AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, METADATA); | ||
| appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder); | ||
| appendSASTokenToQuery(path, SASTokenProvider.SET_PROPERTIES_OPERATION, abfsUriQueryBuilder); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
| final AbfsRestOperation op = getAbfsRestOperation( | ||
|
|
@@ -1194,7 +1197,7 @@ public AbfsRestOperation getPathStatus(final String path, | |
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(HttpQueryParams.QUERY_PARAM_UPN, | ||
| String.valueOf(getAbfsConfiguration().isUpnUsed())); | ||
| appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, | ||
| appendSASTokenToQuery(path, SASTokenProvider.GET_PROPERTIES_OPERATION, | ||
| abfsUriQueryBuilder); | ||
|
|
||
| final URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
|
|
@@ -1271,7 +1274,7 @@ public AbfsRestOperation read(final String path, | |
| } | ||
|
|
||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.FIXED_SAS_STORE_OPERATION, | ||
| String sasTokenForReuse = appendSASTokenToQuery(path, SASTokenProvider.READ_OPERATION, | ||
| abfsUriQueryBuilder, cachedSasToken); | ||
|
|
||
| URL url = createRequestUrl(path, abfsUriQueryBuilder.toString()); | ||
|
|
@@ -1433,7 +1436,7 @@ public AbfsRestOperation getBlockList(final String path, | |
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
|
|
||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| String operation = SASTokenProvider.FIXED_SAS_STORE_OPERATION; | ||
| String operation = SASTokenProvider.READ_OPERATION; | ||
| appendSASTokenToQuery(path, operation, abfsUriQueryBuilder); | ||
|
|
||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_COMP, BLOCKLIST); | ||
|
|
@@ -1471,9 +1474,9 @@ public AbfsRestOperation copyBlob(Path sourceBlobPath, | |
| String dstBlobRelativePath = destinationBlobPath.toUri().getPath(); | ||
| String srcBlobRelativePath = sourceBlobPath.toUri().getPath(); | ||
| appendSASTokenToQuery(dstBlobRelativePath, | ||
| SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilderDst); | ||
| SASTokenProvider.COPY_BLOB_DST_OPERATION, abfsUriQueryBuilderDst); | ||
| appendSASTokenToQuery(srcBlobRelativePath, | ||
| SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilderSrc); | ||
| SASTokenProvider.COPY_BLOB_SRC_OPERATION, abfsUriQueryBuilderSrc); | ||
| final URL url = createRequestUrl(dstBlobRelativePath, | ||
| abfsUriQueryBuilderDst.toString()); | ||
| final String sourcePathUrl = createRequestUrl(srcBlobRelativePath, | ||
|
|
@@ -1507,7 +1510,7 @@ public AbfsRestOperation deleteBlobPath(final Path blobPath, | |
| AbfsUriQueryBuilder abfsUriQueryBuilder = createDefaultUriQueryBuilder(); | ||
| String blobRelativePath = blobPath.toUri().getPath(); | ||
| appendSASTokenToQuery(blobRelativePath, | ||
| SASTokenProvider.FIXED_SAS_STORE_OPERATION, abfsUriQueryBuilder); | ||
| SASTokenProvider.DELETE_OPERATION, abfsUriQueryBuilder); | ||
| final URL url = createRequestUrl(blobRelativePath, | ||
| abfsUriQueryBuilder.toString()); | ||
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -663,13 +663,13 @@ To know more about how SAS Authentication works refer to | |
| [Grant limited access to Azure Storage resources using shared access signatures (SAS)](https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview) | ||
|
|
||
| There are three types of SAS supported by Azure Storage: | ||
| - [User Delegation SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas): Recommended for use with ABFS Driver with HNS Enabled ADLS Gen2 accounts. It is Identity based SAS that works at blob/directory level) | ||
| - [User Delegation SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas): Recommended for use with ABFS Driver with HNS Enabled ADLS Gen2 accounts or HNS-Disabled Blob Storage accounts. It is Identity based SAS that works at blob/directory level) | ||
|
||
| - [Service SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas): Global and works at container level. | ||
| - [Account SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-account-sas): Global and works at account level. | ||
|
|
||
| #### Known Issues With SAS | ||
| - SAS Based Authentication works only with HNS Enabled ADLS Gen2 Accounts which | ||
| is a recommended account type to be used with ABFS. | ||
| - SAS Based Authentication works with HNS Enabled ADLS Gen2 Accounts (which | ||
|
||
| is a recommended account type to be used with ABFS) and HNS-Disabled Blob Storage accounts. | ||
| - Certain root level operations are known to fail with SAS Based Authentication. | ||
|
|
||
| #### Using User Delegation SAS with ABFS | ||
|
|
@@ -737,7 +737,7 @@ the following configurations apart from above two: | |
|
|
||
| - **Security**: More secure than Shared Key and allows granting limited access | ||
| to data without exposing the access key. Recommended to be used only with HNS Enabled, | ||
| ADLS Gen 2 storage accounts. | ||
| ADLS Gen 2 storage accounts or HNS-Disabled Blob Storage accounts. | ||
|
||
|
|
||
| #### Using Account/Service SAS with ABFS | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,14 +91,17 @@ public ITestAzureBlobFileSystemDelegationSAS() throws Exception { | |
| public void setup() throws Exception { | ||
| isHNSEnabled = this.getConfiguration().getBoolean( | ||
| TestConfigurationKeys.FS_AZURE_TEST_NAMESPACE_ENABLED_ACCOUNT, false); | ||
| Assume.assumeTrue(isHNSEnabled); | ||
| if(!isHNSEnabled){ | ||
|
||
| assumeBlobServiceType(); | ||
| } | ||
| createFilesystemForSASTests(); | ||
| super.setup(); | ||
| } | ||
|
|
||
| @Test | ||
| // Test filesystem operations access, create, mkdirs, setOwner, getFileStatus | ||
| public void testCheckAccess() throws Exception { | ||
| assumeHnsEnabled(); | ||
| final AzureBlobFileSystem fs = getFileSystem(); | ||
|
|
||
| Path rootPath = new Path("/"); | ||
|
|
@@ -217,6 +220,7 @@ public void testReadAndWrite() throws Exception { | |
|
|
||
| @Test | ||
| public void checkExceptionForRenameOverwrites() throws Exception { | ||
| assumeHnsEnabled(); | ||
anujmodi2021 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| final AzureBlobFileSystem fs = getFileSystem(); | ||
|
|
||
| Path src = new Path("a/b/f1.txt"); | ||
|
|
@@ -315,6 +319,7 @@ public void testList() throws Exception { | |
| // Test filesystem operations setAcl, getAclStatus, removeAcl | ||
| // setPermissions and getFileStatus | ||
| public void testAcl() throws Exception { | ||
| assumeHnsEnabled(); | ||
| final AzureBlobFileSystem fs = getFileSystem(); | ||
| Path reqPath = new Path(UUID.randomUUID().toString()); | ||
|
|
||
|
|
@@ -344,6 +349,7 @@ public void testAcl() throws Exception { | |
| @Test | ||
| // Test getFileStatus and getAclStatus operations on root path | ||
| public void testRootPath() throws Exception { | ||
| assumeHnsEnabled(); | ||
| final AzureBlobFileSystem fs = getFileSystem(); | ||
| Path rootPath = new Path(AbfsHttpConstants.ROOT_PATH); | ||
|
|
||
|
|
@@ -443,6 +449,7 @@ null, getTestTracingContext(getFileSystem(), false), | |
| @Test | ||
| // SetPermission should fail when saoid is not the owner and succeed when it is. | ||
| public void testSetPermissionForNonOwner() throws Exception { | ||
| assumeHnsEnabled(); | ||
| final AzureBlobFileSystem fs = getFileSystem(); | ||
|
|
||
| Path rootPath = new Path("/"); | ||
|
|
@@ -478,6 +485,7 @@ public void testSetPermissionForNonOwner() throws Exception { | |
| @Test | ||
| // Without saoid or suoid, setPermission should succeed with sp=p for a non-owner. | ||
| public void testSetPermissionWithoutAgentForNonOwner() throws Exception { | ||
| assumeHnsEnabled(); | ||
| final AzureBlobFileSystem fs = getFileSystem(); | ||
| Path path = new Path(MockDelegationSASTokenProvider.NO_AGENT_PATH); | ||
| fs.create(path).close(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,6 +60,8 @@ public String getDelegationSAS(String accountName, String containerName, String | |
| case SASTokenProvider.CREATE_DIRECTORY_OPERATION: | ||
| case SASTokenProvider.WRITE_OPERATION: | ||
| case SASTokenProvider.SET_PROPERTIES_OPERATION: | ||
| case SASTokenProvider.LEASE_BLOB_OPERATION: | ||
| case SASTokenProvider.COPY_BLOB_DST_OPERATION: | ||
| sp = "w"; | ||
| break; | ||
| case SASTokenProvider.DELETE_OPERATION: | ||
|
|
@@ -75,11 +77,16 @@ public String getDelegationSAS(String accountName, String containerName, String | |
| case SASTokenProvider.GET_STATUS_OPERATION: | ||
| sp = "e"; | ||
| break; | ||
| case SASTokenProvider.LIST_OPERATION_BLOB: | ||
| sp = "l"; | ||
| sr="c"; | ||
|
||
| break; | ||
| case SASTokenProvider.LIST_OPERATION: | ||
| sp = "l"; | ||
| break; | ||
| case SASTokenProvider.GET_PROPERTIES_OPERATION: | ||
| case SASTokenProvider.READ_OPERATION: | ||
| case SASTokenProvider.COPY_BLOB_SRC_OPERATION: | ||
| sp = "r"; | ||
| break; | ||
| case SASTokenProvider.RENAME_DESTINATION_OPERATION: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.