Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/storage/azure-storage-blob/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "java",
"TagPrefix": "java/storage/azure-storage-blob",
"Tag": "java/storage/azure-storage-blob_4ab10936db"
"Tag": "java/storage/azure-storage-blob_69330cd83d"
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ public void beforeTest() {
new TestProxySanitizer("x-ms-copy-source-authorization", ".+", "REDACTED",
TestProxySanitizerType.HEADER),
new TestProxySanitizer("x-ms-rename-source", "((?<=http://|https://)([^/?]+)|sig=(.*))", "REDACTED",
TestProxySanitizerType.HEADER)));
TestProxySanitizerType.HEADER),
new TestProxySanitizer("skoid=([^&]+)", "REDACTED", TestProxySanitizerType.URL)));
}

// Ignore changes to the order of query parameters and wholly ignore the 'sv' (service version) query parameter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@
import reactor.util.function.Tuples;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

Expand Down Expand Up @@ -583,6 +586,130 @@ public void containerSasFilterBlobsFail() {
StepVerifier.create(client.setTags(tags)).verifyError(BlobStorageException.class);
}

@Test
@RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-04-06")
public void createPermissionUpload() {
liveTestScenarioWithRetry(() -> {
BlobServiceAsyncClient oauthService = getOAuthServiceAsyncClient();
BlobContainerAsyncClient oauthContainer
= oauthService.getBlobContainerAsyncClient(cc.getBlobContainerName());

String oauthBlobName = generateBlobName();
OffsetDateTime expiryTime = testResourceNamer.now().plusDays(1);

Mono<Void> response = oauthService.getUserDelegationKey(null, expiryTime).flatMap(key -> {
key.setSignedTenantId(testResourceNamer.recordValueFromConfig(key.getSignedTenantId()));
key.setSignedObjectId(testResourceNamer.recordValueFromConfig(key.getSignedObjectId()));
String saoid = testResourceNamer.randomUuid();

BlobSasPermission permissions = new BlobSasPermission().setCreatePermission(true);
BlobServiceSasSignatureValues sasValues
= new BlobServiceSasSignatureValues(expiryTime, permissions).setPreauthorizedAgentObjectId(saoid);

String sasWithPermissions = oauthContainer.generateUserDelegationSas(sasValues, key);

BlockBlobAsyncClient blockClient
= instrument(new SpecializedBlobClientBuilder().endpoint(oauthContainer.getBlobContainerUrl())
.blobName(oauthBlobName)
.sasToken(sasWithPermissions)).buildBlockBlobAsyncClient();

return blockClient.upload(DATA.getDefaultFlux(), DATA.getDefaultDataSize()).then();
});

StepVerifier.create(response).verifyComplete();
});
}

@Test
@RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-04-06")
public void transferBlobWithCreatePermission() {
liveTestScenarioWithRetry(() -> {
BlobServiceAsyncClient oauthService = getOAuthServiceAsyncClient();
String containerName = ccAsync.getBlobContainerName();
BlobContainerAsyncClient oauthContainer = oauthService.getBlobContainerAsyncClient(containerName);

String sourceBlobName = generateBlobName();
String destinationBlobName = generateBlobName();
OffsetDateTime expiryTime = testResourceNamer.now().plusDays(1);

// Upload source blob via OAuth client
BlockBlobAsyncClient sourceBlob
= oauthContainer.getBlobAsyncClient(sourceBlobName).getBlockBlobAsyncClient();
sourceBlob.upload(DATA.getDefaultFlux(), DATA.getDefaultDataSize()).block();

Mono<Void> response = oauthService.getUserDelegationKey(null, expiryTime).flatMap(key -> {

key.setSignedTenantId(testResourceNamer.recordValueFromConfig(key.getSignedTenantId()));
key.setSignedObjectId(testResourceNamer.recordValueFromConfig(key.getSignedObjectId()));
String saoid = testResourceNamer.randomUuid();

// Create-only permission for destination blob
BlobSasPermission destinationPermissions = new BlobSasPermission().setCreatePermission(true);
BlobServiceSasSignatureValues sasValues
= new BlobServiceSasSignatureValues(expiryTime, destinationPermissions)
.setPreauthorizedAgentObjectId(saoid);
String createPermissionsOnly = oauthContainer.generateUserDelegationSas(sasValues, key);
BlockBlobAsyncClient destinationClient
= instrument(new SpecializedBlobClientBuilder().endpoint(oauthContainer.getBlobContainerUrl())
.blobName(destinationBlobName)
.sasToken(createPermissionsOnly)).buildBlockBlobAsyncClient();

// Read permission for source blob
BlobSasPermission readPermission = new BlobSasPermission().setReadPermission(true);
BlobServiceSasSignatureValues readValues = new BlobServiceSasSignatureValues(expiryTime, readPermission)
.setPreauthorizedAgentObjectId(saoid);
String readSas = oauthContainer.generateUserDelegationSas(readValues, key);
String sourceUrl = sourceBlob.getBlobUrl() + "?" + readSas;

return destinationClient.copyFromUrl(sourceUrl).then();
});

StepVerifier.create(response).verifyComplete();
});
}

@Test
@RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-04-06")
public void commitBlockListWithCreatePermission() {
liveTestScenarioWithRetry(() -> {
BlobServiceAsyncClient oauthService = getOAuthServiceAsyncClient();
String containerName = ccAsync.getBlobContainerName();
BlobContainerAsyncClient oauthContainer = oauthService.getBlobContainerAsyncClient(containerName);
String blockId = Base64.getEncoder().encodeToString("blockid".getBytes(StandardCharsets.UTF_8));
List<String> blockIds = new ArrayList<>();
blockIds.add(blockId);

String destinationBlobName = generateBlobName();
OffsetDateTime expiryTime = testResourceNamer.now().plusDays(1);

Mono<Void> response = oauthService.getUserDelegationKey(null, expiryTime).flatMap(key -> {

key.setSignedTenantId(testResourceNamer.recordValueFromConfig(key.getSignedTenantId()));
key.setSignedObjectId(testResourceNamer.recordValueFromConfig(key.getSignedObjectId()));
String saoid = testResourceNamer.randomUuid();

// Create-only permission for destination blob
BlobSasPermission destinationPermissions = new BlobSasPermission().setCreatePermission(true);
BlobServiceSasSignatureValues sasValues
= new BlobServiceSasSignatureValues(expiryTime, destinationPermissions)
.setPreauthorizedAgentObjectId(saoid);
String createPermissionsOnly = oauthContainer.generateUserDelegationSas(sasValues, key);
BlockBlobAsyncClient destinationClient
= instrument(new SpecializedBlobClientBuilder().endpoint(oauthContainer.getBlobContainerUrl())
.blobName(destinationBlobName)
.sasToken(createPermissionsOnly)).buildBlockBlobAsyncClient();

Flux<ByteBuffer> data = DATA.getDefaultFlux();

return destinationClient.stageBlock(blockId, data, DATA.getDefaultDataSize())
.then(destinationClient.commitBlockList(blockIds, false))
.then();
});

StepVerifier.create(response).verifyComplete();
});
}

// RBAC replication lag
@Test
public void blobUserDelegationSaoid() {
Expand Down Expand Up @@ -1484,4 +1611,5 @@ public void blobSasUserDelegationDelegatedTenantIdFail() {
e -> assertExceptionStatusCodeAndMessage(e, 403, BlobErrorCode.AUTHENTICATION_FAILED));
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,13 @@
import org.junit.jupiter.params.provider.ValueSource;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

Expand Down Expand Up @@ -1296,6 +1299,115 @@ public void blobSasImplUtilCanonicalizedResource(String containerName, String bl
assertEquals(expectedResource, queryParams.getResource());
}

@Test
@RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-04-06")
public void createPermissionUpload() {
liveTestScenarioWithRetry(() -> {
BlobServiceClient oauthService = getOAuthServiceClient();
String oauthContainerName = cc.getBlobContainerName();
BlobContainerClient oauthContainer = oauthService.getBlobContainerClient(oauthContainerName);

String oauthBlobName = generateBlobName();
OffsetDateTime expiryTime = testResourceNamer.now().plusDays(1);

UserDelegationKey key = oauthService.getUserDelegationKey(null, expiryTime);
key.setSignedTenantId(testResourceNamer.recordValueFromConfig(key.getSignedTenantId()));
key.setSignedObjectId(testResourceNamer.recordValueFromConfig(key.getSignedObjectId()));
String saoid = testResourceNamer.randomUuid();

BlobSasPermission permissions = new BlobSasPermission().setCreatePermission(true);
BlobServiceSasSignatureValues sasValues
= new BlobServiceSasSignatureValues(expiryTime, permissions).setPreauthorizedAgentObjectId(saoid);

String sasWithPermissions = oauthContainer.generateUserDelegationSas(sasValues, key);
BlockBlobClient blockClient
= instrument(new SpecializedBlobClientBuilder().endpoint(oauthContainer.getBlobContainerUrl())
.blobName(oauthBlobName)
.sasToken(sasWithPermissions)).buildBlockBlobClient();

assertDoesNotThrow(() -> blockClient.upload(DATA.getDefaultInputStream(), DATA.getDefaultDataSize()));
});
}

@Test
@RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-04-06")
public void transferBlobWithCreatePermission() {
liveTestScenarioWithRetry(() -> {
BlobServiceClient oauthService = getOAuthServiceClient();
String containerName = cc.getBlobContainerName();
BlobContainerClient oauthContainer = oauthService.getBlobContainerClient(containerName);

String sourceBlobName = generateBlobName();
String destinationBlobName = generateBlobName();
OffsetDateTime expiryTime = testResourceNamer.now().plusDays(1);

// Upload source blob via OAuth client
BlockBlobClient sourceBlob = oauthContainer.getBlobClient(sourceBlobName).getBlockBlobClient();
sourceBlob.upload(DATA.getDefaultInputStream(), DATA.getDefaultDataSize());

UserDelegationKey key = oauthService.getUserDelegationKey(null, expiryTime);
key.setSignedTenantId(testResourceNamer.recordValueFromConfig(key.getSignedTenantId()));
key.setSignedObjectId(testResourceNamer.recordValueFromConfig(key.getSignedObjectId()));
String saoid = testResourceNamer.randomUuid();

// Create-only permission for destination blob
BlobSasPermission destinationPermissions = new BlobSasPermission().setCreatePermission(true);
BlobServiceSasSignatureValues sasValues
= new BlobServiceSasSignatureValues(expiryTime, destinationPermissions)
.setPreauthorizedAgentObjectId(saoid);
String createPermissionsOnly = oauthContainer.generateUserDelegationSas(sasValues, key);
BlockBlobClient destinationClient
= instrument(new SpecializedBlobClientBuilder().endpoint(oauthContainer.getBlobContainerUrl())
.blobName(destinationBlobName)
.sasToken(createPermissionsOnly)).buildBlockBlobClient();

// Read permission for source blob
BlobSasPermission readPermission = new BlobSasPermission().setReadPermission(true);
BlobServiceSasSignatureValues readValues
= new BlobServiceSasSignatureValues(expiryTime, readPermission).setPreauthorizedAgentObjectId(saoid);
String readSas = oauthContainer.generateUserDelegationSas(readValues, key);
String sourceUrl = sourceBlob.getBlobUrl() + "?" + readSas;

assertDoesNotThrow(() -> destinationClient.copyFromUrl(sourceUrl));
});
}

@Test
@RequiredServiceVersion(clazz = BlobServiceVersion.class, min = "2026-04-06")
public void commitBlockListWithCreatePermission() {
liveTestScenarioWithRetry(() -> {
BlobServiceClient oauthService = getOAuthServiceClient();
String containerName = cc.getBlobContainerName();
BlobContainerClient oauthContainer = oauthService.getBlobContainerClient(containerName);
String blockId = Base64.getEncoder().encodeToString("blockid".getBytes(StandardCharsets.UTF_8));
List<String> blockIds = new ArrayList<>();
blockIds.add(blockId);

String destinationBlobName = generateBlobName();
OffsetDateTime expiryTime = testResourceNamer.now().plusDays(1);

UserDelegationKey key = oauthService.getUserDelegationKey(null, expiryTime);
key.setSignedTenantId(testResourceNamer.recordValueFromConfig(key.getSignedTenantId()));
key.setSignedObjectId(testResourceNamer.recordValueFromConfig(key.getSignedObjectId()));
String saoid = testResourceNamer.randomUuid();

// Create-only permission for destination blob
BlobSasPermission destinationPermissions = new BlobSasPermission().setCreatePermission(true);
BlobServiceSasSignatureValues sasValues
= new BlobServiceSasSignatureValues(expiryTime, destinationPermissions)
.setPreauthorizedAgentObjectId(saoid);
String createPermissionsOnly = oauthContainer.generateUserDelegationSas(sasValues, key);
BlockBlobClient destinationClient
= instrument(new SpecializedBlobClientBuilder().endpoint(oauthContainer.getBlobContainerUrl())
.blobName(destinationBlobName)
.sasToken(createPermissionsOnly)).buildBlockBlobClient();

destinationClient.stageBlock(blockId, DATA.getDefaultInputStream(), DATA.getDefaultDataSize());

assertDoesNotThrow(() -> destinationClient.commitBlockList(blockIds, false));
});
}

private static Stream<Arguments> blobSasImplUtilCanonicalizedResourceSupplier() {
return Stream.of(
Arguments.of("c", "b", "id", OffsetDateTime.now(), "bs",
Expand Down
Loading