Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4c94ea6
generate base files based on swagger docs
browndav-msft Mar 30, 2026
d561535
create live tests for createSession
browndav-msft Apr 1, 2026
1020227
add recordings
browndav-msft Apr 1, 2026
1d905f0
create new files based on swagger update
browndav-msft Apr 2, 2026
9f8e365
add two params to BlobContainerClient#createSessionWithResponse
browndav-msft Apr 2, 2026
e63fe17
add sanitizers for SessionToken and SessionKey to BlobTestBase
browndav-msft Apr 3, 2026
3e2fb11
add recording for createSessionReturnsTokenAndKey
browndav-msft Apr 3, 2026
3968a0e
create StorageSessionCredential with isExpired
browndav-msft Apr 3, 2026
09dcce6
create BlobSessionClient so that BlobSessionProvider takes it as a de…
browndav-msft Apr 4, 2026
bea226b
create BlobSEssionClient with tests
browndav-msft Apr 6, 2026
e0b4440
add recorings for BlobSessionClient
browndav-msft Apr 6, 2026
0a3570e
fix BlobContainerAsyncClient to match new swagger, add new recording
browndav-msft Apr 6, 2026
6a026f3
add SessionProvider and SessionProviderTest
browndav-msft Apr 6, 2026
f6ca3e8
add accountName to BlobSessionClient
browndav-msft Apr 6, 2026
a1bdf3a
add accountName to StorageSessionCredential and SesionTestHelper
browndav-msft Apr 6, 2026
7309432
wip
browndav-msft Apr 7, 2026
9aa9bab
change sessionprovider to SEssionTokenCredentialPolicy
browndav-msft Apr 9, 2026
8a620a1
wip
browndav-msft Apr 14, 2026
925c44b
move session tests from containerapi to blobsessionclienttests
browndav-msft Apr 14, 2026
798b821
fix blobsessiontests and add place holder for end-to-end tests in con…
browndav-msft Apr 14, 2026
ced6225
add recordings for blobsessionclient
browndav-msft Apr 15, 2026
a93e06a
linting
browndav-msft Apr 15, 2026
301189a
refactor cache into separate class so it follows BearerTokenAuthentic…
browndav-msft Apr 15, 2026
9c3b2e3
add 503 fallback
browndav-msft Apr 15, 2026
0e5a6b0
add tests for udsas, but disabled for now
browndav-msft Apr 15, 2026
4cc43cf
refactor createContext to use hardcoded endpoint
browndav-msft Apr 16, 2026
b3a5774
add SessionMode and tests for SessionMode
browndav-msft Apr 16, 2026
4c87689
add sessionOptions to buildPipeline, add null to builders not using s…
browndav-msft Apr 17, 2026
7e5b122
move SessionTokenCredentialPolicy ahead of StorageBearerTokenChalleng…
browndav-msft Apr 18, 2026
18a4f67
fix linting issues
browndav-msft Apr 18, 2026
05e4b8f
add session to BlobServiceClients and BlobServiceClientBuildeer
browndav-msft Apr 20, 2026
a988a18
change expiration so that it defaults to 5 minutes, if there is no ex…
browndav-msft Apr 20, 2026
1762ea3
move SessionOptions so that it is public
browndav-msft Apr 20, 2026
c2c10a5
remove old SessionOptions
browndav-msft Apr 20, 2026
9e5404a
remove unnecessary references to containerName and serviceVersion
browndav-msft Apr 20, 2026
a147cc7
add BlobContainerSessionInfo, add other Copilot recommendations
browndav-msft Apr 20, 2026
e7c047e
delete BlobContainerSessionInfo, restore return CreateSessionResponse
browndav-msft Apr 20, 2026
fc6ee15
create createSession end-to-end test with recordings
browndav-msft Apr 20, 2026
e157d96
only allow get requests for getblob
browndav-msft Apr 20, 2026
cba8194
wrap tests in try-with-resources
browndav-msft Apr 20, 2026
83e5710
make createSession package private
browndav-msft Apr 20, 2026
13e26be
fixes based on copilot suggestions
browndav-msft Apr 20, 2026
605a9cb
Update sdk/storage/azure-storage-blob/src/main/java/com/azure/storage…
browndav-msft Apr 20, 2026
08edd49
add containerName to SessionOptions
browndav-msft Apr 21, 2026
9fffc0a
move accountName to SessionOptions
browndav-msft Apr 21, 2026
f1de7cd
refactor: SessionTokenCredentialPolicy accepts bearer policy as const…
browndav-msft Apr 21, 2026
270ade3
refactor: introduce AuthStrategy enum and consolidate analyzeRequest
browndav-msft Apr 22, 2026
f15f2ae
refactor: remove redundant restype check from analyzeRequest
browndav-msft Apr 22, 2026
58a1417
change sessionmode from always to singlespecifciedcontainer, add reso…
browndav-msft Apr 22, 2026
7e49d4e
wrap bearer token in sessioncredentialpolicy
browndav-msft Apr 22, 2026
dda8123
fix NPE for SessionOptions, sessionoptions always non null
browndav-msft Apr 22, 2026
5acd00f
add tests for sessiontokencredpolicy and storagesessioncred
browndav-msft Apr 22, 2026
63698f8
add logic to avoid wrapping Bearertoken, if session is not needed
browndav-msft Apr 22, 2026
52a3fe2
add overloaded oauth in blobtestbase to be able to add sessionoptions
browndav-msft Apr 22, 2026
82599ee
add overloaded getOAuthServiceAsyncClient to be able to pass session …
browndav-msft Apr 22, 2026
ff40db4
add custom buildStringToSign to remove `0` from get requests
browndav-msft Apr 22, 2026
0af3885
readd versions
browndav-msft Apr 23, 2026
04d232c
readd ci.system.properties
browndav-msft Apr 23, 2026
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_47f4243e59"
"Tag": "java/storage/azure-storage-blob_4f95fc8478"
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ private HttpPipeline constructPipeline() {
? httpPipeline
: BuilderHelper.buildPipeline(storageSharedKeyCredential, tokenCredential, azureSasCredential, sasToken,
endpoint, retryOptions, coreRetryOptions, logOptions, clientOptions, httpClient, perCallPolicies,
perRetryPolicies, configuration, audience, LOGGER);
perRetryPolicies, configuration, audience, LOGGER, null, null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
import com.azure.storage.blob.implementation.models.EncryptionScope;
import com.azure.storage.blob.implementation.models.ListBlobsFlatSegmentResponse;
import com.azure.storage.blob.implementation.models.ListBlobsHierarchySegmentResponse;
import com.azure.storage.blob.implementation.models.AuthenticationType;
import com.azure.storage.blob.implementation.models.CreateSessionConfiguration;
import com.azure.storage.blob.implementation.models.CreateSessionResponse;
import com.azure.storage.blob.implementation.util.BlobConstants;
import com.azure.storage.blob.implementation.util.BlobSasImplUtil;
import com.azure.storage.blob.implementation.util.ModelHelper;
Expand Down Expand Up @@ -1691,11 +1694,39 @@ public String generateSas(BlobServiceSasSignatureValues blobServiceSasSignatureV
.generateSas(SasImplUtils.extractSharedKeyCredential(getHttpPipeline()), stringToSignHandler, context);
}

// private boolean validateNoTime(BlobRequestConditions modifiedRequestConditions) {
// if (modifiedRequestConditions == null) {
// return true;
// }
// return modifiedRequestConditions.getIfModifiedSince() == null
// && modifiedRequestConditions.getIfUnmodifiedSince() == null;
// }
/**
* Creates a session scoped to this container. The session provides temporary credentials (a session token and
* session key) that can be used to sign subsequent requests using the Shared Key protocol.
*
* @return A {@link Mono} containing the {@link CreateSessionResponse} with session credentials.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
Mono<CreateSessionResponse> createSession() {
return createSessionWithResponse().flatMap(FluxUtil::toMono);
}
Comment thread
browndav-msft marked this conversation as resolved.

/**
* Creates a session scoped to this container. The session provides temporary credentials (a session token and
* session key) that can be used to sign subsequent requests using the Shared Key protocol.
*
* @return A {@link Mono} containing a {@link Response} with the {@link CreateSessionResponse}.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
Mono<Response<CreateSessionResponse>> createSessionWithResponse() {
try {
Comment thread
browndav-msft marked this conversation as resolved.
return withContext(this::createSessionWithResponse);
} catch (RuntimeException ex) {
return monoError(LOGGER, ex);
}
}

Mono<Response<CreateSessionResponse>> createSessionWithResponse(Context context) {
context = context == null ? Context.NONE : context;
CreateSessionConfiguration config
= new CreateSessionConfiguration().setAuthenticationType(AuthenticationType.HMAC);
return this.azureBlobStorage.getContainers()
.createSessionWithResponseAsync(containerName, config, null, null, context)
.map(response -> new SimpleResponse<>(response, response.getValue()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
import com.azure.storage.blob.implementation.models.FilterBlobSegment;
import com.azure.storage.blob.implementation.models.ListBlobsFlatSegmentResponse;
import com.azure.storage.blob.implementation.models.ListBlobsHierarchySegmentResponse;
import com.azure.storage.blob.implementation.models.AuthenticationType;
import com.azure.storage.blob.implementation.models.CreateSessionConfiguration;
import com.azure.storage.blob.implementation.models.CreateSessionResponse;
import com.azure.storage.blob.implementation.util.BlobConstants;
import com.azure.storage.blob.implementation.util.BlobSasImplUtil;
import com.azure.storage.blob.implementation.util.ModelHelper;
Expand Down Expand Up @@ -1509,4 +1512,37 @@ public String generateSas(BlobServiceSasSignatureValues blobServiceSasSignatureV
.generateSas(SasImplUtils.extractSharedKeyCredential(getHttpPipeline()), stringToSignHandler, context);
}

/**
* Creates a session scoped to this container. The session provides temporary credentials (a session token and
* session key) that can be used to sign subsequent requests using the Shared Key protocol.
*
* @return The {@link CreateSessionResponse} with session credentials.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
CreateSessionResponse createSession() {
return createSessionWithResponse(null, Context.NONE).getValue();
Comment on lines +1521 to +1523
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

These createSession* methods are package-private even though they’re annotated with @ServiceMethod and return a generated implementation.models.* type. If Create Session is intended to be a customer-facing API, these should be public and ideally return a public model type; otherwise, consider removing @ServiceMethod to avoid implying a supported public surface.

Copilot uses AI. Check for mistakes.
}

/**
* Creates a session scoped to this container. The session provides temporary credentials (a session token and
* session key) that can be used to sign subsequent requests using the Shared Key protocol.
*
* @param timeout An optional timeout value beyond which a {@link RuntimeException} will be raised.
* @param context Additional context that is passed through the Http pipeline during the service call.
* @return A {@link Response} containing the {@link CreateSessionResponse}.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
Response<CreateSessionResponse> createSessionWithResponse(Duration timeout, Context context) {
Context finalContext = context == null ? Context.NONE : context;
CreateSessionConfiguration config
= new CreateSessionConfiguration().setAuthenticationType(AuthenticationType.HMAC);

Callable<Response<CreateSessionResponse>> operation = () -> {
Response<CreateSessionResponse> response = this.azureBlobStorage.getContainers()
.createSessionWithResponse(containerName, config, null, null, finalContext);
return new SimpleResponse<>(response, response.getValue());
};

return sendRequest(operation, timeout, BlobStorageException.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import com.azure.storage.blob.models.BlobContainerEncryptionScope;
import com.azure.storage.blob.models.CpkInfo;
import com.azure.storage.blob.models.CustomerProvidedKey;
import com.azure.storage.blob.models.SessionOptions;
import com.azure.storage.blob.models.SessionMode;
import com.azure.storage.common.StorageSharedKeyCredential;
import com.azure.storage.common.implementation.connectionstring.StorageAuthenticationSettings;
import com.azure.storage.common.implementation.connectionstring.StorageConnectionString;
Expand Down Expand Up @@ -91,6 +93,7 @@ public final class BlobContainerClientBuilder implements TokenCredentialTrait<Bl
private Configuration configuration;
private BlobServiceVersion version;
private BlobAudience audience;
private SessionOptions sessionOptions = new SessionOptions();

/**
* Creates a builder instance that is able to configure and construct {@link BlobContainerClient ContainerClients}
Expand Down Expand Up @@ -124,6 +127,8 @@ public BlobContainerClient buildClient() {
new IllegalArgumentException("Customer provided key and encryption " + "scope cannot both be set"));
}

validateSessionMode();

/*
Implicit and explicit root container access are functionally equivalent, but explicit references are easier
to read and debug.
Expand All @@ -133,7 +138,7 @@ public BlobContainerClient buildClient() {

BlobServiceVersion serviceVersion = version != null ? version : BlobServiceVersion.getLatest();

HttpPipeline pipeline = constructPipeline();
HttpPipeline pipeline = constructPipeline(blobContainerName, serviceVersion);

return new BlobContainerClient(pipeline, endpoint, serviceVersion, accountName, blobContainerName,
customerProvidedKey, encryptionScope, blobContainerEncryptionScope);
Expand Down Expand Up @@ -165,6 +170,8 @@ public BlobContainerAsyncClient buildAsyncClient() {
new IllegalArgumentException("Customer provided key and encryption " + "scope cannot both be set"));
}

validateSessionMode();

/*
Implicit and explicit root container access are functionally equivalent, but explicit references are easier
to read and debug.
Expand All @@ -174,18 +181,32 @@ public BlobContainerAsyncClient buildAsyncClient() {

BlobServiceVersion serviceVersion = version != null ? version : BlobServiceVersion.getLatest();

HttpPipeline pipeline = constructPipeline();
HttpPipeline pipeline = constructPipeline(blobContainerName, serviceVersion);

return new BlobContainerAsyncClient(pipeline, endpoint, serviceVersion, accountName, blobContainerName,
customerProvidedKey, encryptionScope, blobContainerEncryptionScope);
}

private HttpPipeline constructPipeline() {
return (httpPipeline != null)
? httpPipeline
: BuilderHelper.buildPipeline(storageSharedKeyCredential, tokenCredential, azureSasCredential, sasToken,
endpoint, retryOptions, coreRetryOptions, logOptions, clientOptions, httpClient, perCallPolicies,
perRetryPolicies, configuration, audience, LOGGER);
private HttpPipeline constructPipeline(String containerName, BlobServiceVersion serviceVersion) {
if (httpPipeline != null) {
return httpPipeline;
}
if (containerName != null) {
sessionOptions.setContainerName(containerName);
}
if (sessionOptions.getAccountName() == null) {
sessionOptions.setAccountName(accountName);
}
return BuilderHelper.buildPipeline(storageSharedKeyCredential, tokenCredential, azureSasCredential, sasToken,
endpoint, retryOptions, coreRetryOptions, logOptions, clientOptions, httpClient, perCallPolicies,
perRetryPolicies, configuration, audience, LOGGER, sessionOptions, serviceVersion);
}

private void validateSessionMode() {
if (sessionOptions.getSessionMode().resolve() != SessionMode.NONE && CoreUtils.isNullOrEmpty(containerName)) {
throw LOGGER.logExceptionAsError(new IllegalArgumentException(
"containerName must be set when using SessionMode." + sessionOptions.getSessionMode()));
}
}

/**
Expand Down Expand Up @@ -606,4 +627,22 @@ public BlobContainerClientBuilder audience(BlobAudience audience) {
this.audience = audience;
return this;
}

/**
* Sets the {@link SessionOptions} that controls how the SDK manages session-based authentication
* for this container.
* <p>
* Sessions amortize authentication and authorization cost across many requests by signing them
* with a lightweight HMAC key instead of a full bearer token. When the session mode within the options
* is set to a value other than {@link SessionMode#NONE},
* {@link #containerName(String) containerName} must also be set.
*
* @param sessionOptions The session options to use. If {@code null}, defaults to {@link SessionMode#AUTO}
* when identity-based authentication (bearer token) is configured.
Comment thread
browndav-msft marked this conversation as resolved.
* @return the updated BlobContainerClientBuilder object.
*/
public BlobContainerClientBuilder sessionOptions(SessionOptions sessionOptions) {
this.sessionOptions = SessionOptions.orDefault(sessionOptions);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import com.azure.storage.blob.implementation.AzureBlobStorageImplBuilder;
import com.azure.storage.blob.implementation.models.EncryptionScope;
import com.azure.storage.blob.implementation.models.ServicesGetAccountInfoHeaders;
import com.azure.storage.blob.implementation.util.BuilderHelper;
import com.azure.storage.blob.implementation.util.ModelHelper;
import com.azure.storage.blob.models.SessionOptions;
import com.azure.storage.blob.models.BlobContainerEncryptionScope;
import com.azure.storage.blob.models.BlobContainerItem;
import com.azure.storage.blob.models.BlobCorsRule;
Expand All @@ -33,6 +35,7 @@
import com.azure.storage.blob.models.KeyInfo;
import com.azure.storage.blob.models.ListBlobContainersOptions;
import com.azure.storage.blob.models.PublicAccessType;
import com.azure.storage.blob.models.SessionMode;
import com.azure.storage.blob.models.StorageAccountInfo;
import com.azure.storage.blob.models.TaggedBlobItem;
import com.azure.storage.blob.models.UserDelegationKey;
Expand All @@ -54,6 +57,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -95,6 +99,7 @@ public final class BlobServiceAsyncClient {
private final BlobContainerEncryptionScope blobContainerEncryptionScope; // only used to pass down to container
// clients
private final boolean anonymousAccess;
private final SessionOptions sessionOptions;

/**
* Package-private constructor for use by {@link BlobServiceClientBuilder}.
Expand All @@ -108,10 +113,12 @@ public final class BlobServiceAsyncClient {
* @param encryptionScope Encryption scope used during encryption of the blob's data on the server, pass
* {@code null} to allow the service to use its own encryption.
* @param anonymousAccess Whether the client was built with anonymousAccess
* @param sessionOptions Session options for session-based authentication.
*/
BlobServiceAsyncClient(HttpPipeline pipeline, String url, BlobServiceVersion serviceVersion, String accountName,
CpkInfo customerProvidedKey, EncryptionScope encryptionScope,
BlobContainerEncryptionScope blobContainerEncryptionScope, boolean anonymousAccess) {
BlobContainerEncryptionScope blobContainerEncryptionScope, boolean anonymousAccess,
SessionOptions sessionOptions) {
/* Check to make sure the uri is valid. We don't want the error to occur later in the generated layer
when the sas token has already been applied. */
try {
Expand All @@ -130,6 +137,7 @@ public final class BlobServiceAsyncClient {
this.encryptionScope = encryptionScope;
this.blobContainerEncryptionScope = blobContainerEncryptionScope;
this.anonymousAccess = anonymousAccess;
this.sessionOptions = Objects.requireNonNull(sessionOptions, "'sessionOptions' cannot be null.");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
import com.azure.storage.blob.implementation.models.ServicesGetPropertiesHeaders;
import com.azure.storage.blob.implementation.models.ServicesGetStatisticsHeaders;
import com.azure.storage.blob.implementation.models.ServicesGetUserDelegationKeyHeaders;
import com.azure.storage.blob.implementation.util.BuilderHelper;
import com.azure.storage.blob.implementation.util.ModelHelper;
import com.azure.storage.blob.models.SessionMode;
import com.azure.storage.blob.models.SessionOptions;
import com.azure.storage.blob.models.BlobContainerEncryptionScope;
import com.azure.storage.blob.models.BlobContainerItem;
import com.azure.storage.blob.models.BlobContainerListDetails;
Expand Down Expand Up @@ -60,6 +63,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -91,6 +95,7 @@ public final class BlobServiceClient {
private final BlobContainerEncryptionScope blobContainerEncryptionScope; // only used to pass down to container
// clients
private final boolean anonymousAccess;
private final SessionOptions sessionOptions;

/**
* Package-private constructor for use by {@link BlobServiceClientBuilder}.
Expand All @@ -107,7 +112,8 @@ public final class BlobServiceClient {
*/
BlobServiceClient(HttpPipeline pipeline, String url, BlobServiceVersion serviceVersion, String accountName,
CpkInfo customerProvidedKey, EncryptionScope encryptionScope,
BlobContainerEncryptionScope blobContainerEncryptionScope, boolean anonymousAccess) {
BlobContainerEncryptionScope blobContainerEncryptionScope, boolean anonymousAccess,
SessionOptions sessionOptions) {
/* Check to make sure the uri is valid. We don't want the error to occur later in the generated layer
when the sas token has already been applied. */
try {
Expand All @@ -126,6 +132,7 @@ public final class BlobServiceClient {
this.encryptionScope = encryptionScope;
this.blobContainerEncryptionScope = blobContainerEncryptionScope;
this.anonymousAccess = anonymousAccess;
this.sessionOptions = Objects.requireNonNull(sessionOptions, "'sessionOptions' cannot be null.");
}

/**
Expand All @@ -147,6 +154,7 @@ public BlobContainerClient getBlobContainerClient(String containerName) {
if (CoreUtils.isNullOrEmpty(containerName)) {
containerName = BlobContainerClient.ROOT_CONTAINER_NAME;
}

return new BlobContainerClient(getHttpPipeline(), getAccountUrl(), getServiceVersion(), getAccountName(),
containerName, customerProvidedKey, encryptionScope, blobContainerEncryptionScope);
}
Expand Down
Loading
Loading