diff --git a/sdk/keyvault/azure-security-keyvault-secrets/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-secrets/CHANGELOG.md index 9c2ca54e8377..9457820337cf 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/CHANGELOG.md +++ b/sdk/keyvault/azure-security-keyvault-secrets/CHANGELOG.md @@ -1,17 +1,16 @@ # Release History -## 4.11.0-beta.1 (Unreleased) +## 4.11.0-beta.1 (2026-03-19) ### Features Added -### Breaking Changes +- Added `previousVersion` property to `KeyVaultSecret` and related response models, which returns the version of the previous secret, if applicable. This field is only populated for secrets created after June 1, 2025. +- Added `outContentType` query parameter support to `getSecret` operations, enabling PFX to PEM format conversion for certificate-backed secrets. Available in service version `2025-07-01` and later. ### Bugs Fixed - Fixed an issue where certain `HttpResponseException.getResponse()` calls could cause a `NullPointerException`. ([#47801](https://github.com/Azure/azure-sdk-for-java/issues/47801)) -### Other Changes - ## 4.10.5 (2026-01-29) ### Other Changes diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/SecretClientImpl.java b/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/SecretClientImpl.java index 1563bba24bb7..b2292c5f1d60 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/SecretClientImpl.java +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/SecretClientImpl.java @@ -331,7 +331,7 @@ Response getDeletedSecretSync(@HostParam("vaultBaseUrl") String vaul @UnexpectedResponseExceptionType(HttpResponseException.class) Mono> purgeDeletedSecret(@HostParam("vaultBaseUrl") String vaultBaseUrl, @QueryParam("api-version") String apiVersion, @PathParam("secret-name") String secretName, - @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context); + RequestOptions requestOptions, Context context); @Delete("/deletedsecrets/{secret-name}") @ExpectedResponses({ 204 }) @@ -341,7 +341,7 @@ Mono> purgeDeletedSecret(@HostParam("vaultBaseUrl") String vaultB @UnexpectedResponseExceptionType(HttpResponseException.class) Response purgeDeletedSecretSync(@HostParam("vaultBaseUrl") String vaultBaseUrl, @QueryParam("api-version") String apiVersion, @PathParam("secret-name") String secretName, - @HeaderParam("Accept") String accept, RequestOptions requestOptions, Context context); + RequestOptions requestOptions, Context context); @Post("/deletedsecrets/{secret-name}/recover") @ExpectedResponses({ 200 }) @@ -516,6 +516,7 @@ Response getDeletedSecretsNextSync(@PathParam(value = "nextLink", en * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * } * } * @@ -591,6 +592,7 @@ public Mono> setSecretWithResponseAsync(String secretName, * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * } * } * @@ -641,6 +643,7 @@ public Response setSecretWithResponse(String secretName, BinaryData * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * recoveryId: String (Optional) * scheduledPurgeDate: Long (Optional) * deletedDate: Long (Optional) @@ -691,6 +694,7 @@ public Mono> deleteSecretWithResponseAsync(String secretNam * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * recoveryId: String (Optional) * scheduledPurgeDate: Long (Optional) * deletedDate: Long (Optional) @@ -764,6 +768,7 @@ public Response deleteSecretWithResponse(String secretName, RequestO * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * } * } * @@ -839,6 +844,7 @@ public Mono> updateSecretWithResponseAsync(String secretNam * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * } * } * @@ -867,6 +873,17 @@ public Response updateSecretWithResponse(String secretName, String s * * The GET operation is applicable to any secret stored in Azure Key Vault. This operation requires the secrets/get * permission. + *

Query Parameters

+ * + * + * + * + *
Query Parameters
NameTypeRequiredDescription
outContentTypeStringNoThe media type (MIME type) of the certificate. If a + * supported format is specified, the certificate content is converted to the requested format. Currently, only PFX + * to PEM conversion is supported. If an unsupported format is specified, the request is rejected. If not specified, + * the certificate is returned in its original format without conversion. Allowed values: "application/x-pkcs12", + * "application/x-pem-file".
+ * You can add these to a request with {@link RequestOptions#addQueryParam} *

Response Body Schema

* *
@@ -889,6 +906,7 @@ public Response updateSecretWithResponse(String secretName, String s
      *     }
      *     kid: String (Optional)
      *     managed: Boolean (Optional)
+     *     previousVersion: String (Optional)
      * }
      * }
      * 
@@ -919,6 +937,17 @@ public Mono> getSecretWithResponseAsync(String secretName, * * The GET operation is applicable to any secret stored in Azure Key Vault. This operation requires the secrets/get * permission. + *

Query Parameters

+ * + * + * + * + *
Query Parameters
NameTypeRequiredDescription
outContentTypeStringNoThe media type (MIME type) of the certificate. If a + * supported format is specified, the certificate content is converted to the requested format. Currently, only PFX + * to PEM conversion is supported. If an unsupported format is specified, the request is rejected. If not specified, + * the certificate is returned in its original format without conversion. Allowed values: "application/x-pkcs12", + * "application/x-pem-file".
+ * You can add these to a request with {@link RequestOptions#addQueryParam} *

Response Body Schema

* *
@@ -941,6 +970,7 @@ public Mono> getSecretWithResponseAsync(String secretName,
      *     }
      *     kid: String (Optional)
      *     managed: Boolean (Optional)
+     *     previousVersion: String (Optional)
      * }
      * }
      * 
@@ -1652,6 +1682,7 @@ public PagedIterable getDeletedSecrets(RequestOptions requestOptions * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * recoveryId: String (Optional) * scheduledPurgeDate: Long (Optional) * deletedDate: Long (Optional) @@ -1705,6 +1736,7 @@ public Mono> getDeletedSecretWithResponseAsync(String secre * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * recoveryId: String (Optional) * scheduledPurgeDate: Long (Optional) * deletedDate: Long (Optional) @@ -1747,9 +1779,8 @@ public Response getDeletedSecretWithResponse(String secretName, Requ */ @ServiceMethod(returns = ReturnType.SINGLE) public Mono> purgeDeletedSecretWithResponseAsync(String secretName, RequestOptions requestOptions) { - final String accept = "application/json"; return FluxUtil.withContext(context -> service.purgeDeletedSecret(this.getVaultBaseUrl(), - this.getServiceVersion().getVersion(), secretName, accept, requestOptions, context)); + this.getServiceVersion().getVersion(), secretName, requestOptions, context)); } /** @@ -1769,9 +1800,8 @@ public Mono> purgeDeletedSecretWithResponseAsync(String secretNam */ @ServiceMethod(returns = ReturnType.SINGLE) public Response purgeDeletedSecretWithResponse(String secretName, RequestOptions requestOptions) { - final String accept = "application/json"; return service.purgeDeletedSecretSync(this.getVaultBaseUrl(), this.getServiceVersion().getVersion(), secretName, - accept, requestOptions, Context.NONE); + requestOptions, Context.NONE); } /** @@ -1801,6 +1831,7 @@ public Response purgeDeletedSecretWithResponse(String secretName, RequestO * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * } * } * @@ -1849,6 +1880,7 @@ public Mono> recoverDeletedSecretWithResponseAsync(String s * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * } * } * @@ -1966,6 +1998,7 @@ public Response backupSecretWithResponse(String secretName, RequestO * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * } * } * @@ -2025,6 +2058,7 @@ public Mono> restoreSecretWithResponseAsync(BinaryData para * } * kid: String (Optional) * managed: Boolean (Optional) + * previousVersion: String (Optional) * } * } * diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/ContentType.java b/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/ContentType.java new file mode 100644 index 000000000000..dff791b1a3f5 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/ContentType.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.security.keyvault.secrets.implementation.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.util.ExpandableStringEnum; +import java.util.Collection; + +/** + * The media type (MIME type). + */ +public final class ContentType extends ExpandableStringEnum { + /** + * The PKCS#12 file format. + */ + @Generated + public static final ContentType PFX = fromString("application/x-pkcs12"); + + /** + * The PEM file format. + */ + @Generated + public static final ContentType PEM = fromString("application/x-pem-file"); + + /** + * Creates a new instance of ContentType value. + * + * @deprecated Use the {@link #fromString(String)} factory method. + */ + @Generated + @Deprecated + public ContentType() { + } + + /** + * Creates or finds a ContentType from its string representation. + * + * @param name a name to look for. + * @return the corresponding ContentType. + */ + @Generated + public static ContentType fromString(String name) { + return fromString(name, ContentType.class); + } + + /** + * Gets known ContentType values. + * + * @return known ContentType values. + */ + @Generated + public static Collection values() { + return values(ContentType.class); + } +} diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/DeletedSecretBundle.java b/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/DeletedSecretBundle.java index 4731e6b9d279..10f6cfbb145e 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/DeletedSecretBundle.java +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/DeletedSecretBundle.java @@ -66,6 +66,13 @@ public final class DeletedSecretBundle implements JsonSerializable writer.writeString(element)); + jsonWriter.writeStringField("previousVersion", this.previousVersion); jsonWriter.writeStringField("recoveryId", this.recoveryId); return jsonWriter.writeEndObject(); } @@ -246,6 +265,8 @@ public static DeletedSecretBundle fromJson(JsonReader jsonReader) throws IOExcep deserializedDeletedSecretBundle.kid = reader.getString(); } else if ("managed".equals(fieldName)) { deserializedDeletedSecretBundle.managed = reader.getNullable(JsonReader::getBoolean); + } else if ("previousVersion".equals(fieldName)) { + deserializedDeletedSecretBundle.previousVersion = reader.getString(); } else if ("recoveryId".equals(fieldName)) { deserializedDeletedSecretBundle.recoveryId = reader.getString(); } else if ("scheduledPurgeDate".equals(fieldName)) { diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/SecretBundle.java b/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/SecretBundle.java index c86e8e1403f6..29e9b43f7975 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/SecretBundle.java +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/main/java/com/azure/security/keyvault/secrets/implementation/models/SecretBundle.java @@ -62,6 +62,13 @@ public final class SecretBundle implements JsonSerializable { @Generated private Boolean managed; + /* + * The version of the previous certificate, if applicable. Applies only to certificates created after June 1, 2025. + * Certificates created before this date are not retroactively updated. + */ + @Generated + private String previousVersion; + /** * Creates an instance of SecretBundle class. */ @@ -141,6 +148,17 @@ public Boolean isManaged() { return this.managed; } + /** + * Get the previousVersion property: The version of the previous certificate, if applicable. Applies only to + * certificates created after June 1, 2025. Certificates created before this date are not retroactively updated. + * + * @return the previousVersion value. + */ + @Generated + public String getPreviousVersion() { + return this.previousVersion; + } + /** * {@inheritDoc} */ @@ -153,6 +171,7 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { jsonWriter.writeStringField("contentType", this.contentType); jsonWriter.writeJsonField("attributes", this.attributes); jsonWriter.writeMapField("tags", this.tags, (writer, element) -> writer.writeString(element)); + jsonWriter.writeStringField("previousVersion", this.previousVersion); return jsonWriter.writeEndObject(); } @@ -187,6 +206,8 @@ public static SecretBundle fromJson(JsonReader jsonReader) throws IOException { deserializedSecretBundle.kid = reader.getString(); } else if ("managed".equals(fieldName)) { deserializedSecretBundle.managed = reader.getNullable(JsonReader::getBoolean); + } else if ("previousVersion".equals(fieldName)) { + deserializedSecretBundle.previousVersion = reader.getString(); } else { reader.skipChildren(); } diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/test/java/com/azure/security/keyvault/secrets/SecretClientTestBase.java b/sdk/keyvault/azure-security-keyvault-secrets/src/test/java/com/azure/security/keyvault/secrets/SecretClientTestBase.java index f2271f308881..c6d764d81f31 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/test/java/com/azure/security/keyvault/secrets/SecretClientTestBase.java +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/test/java/com/azure/security/keyvault/secrets/SecretClientTestBase.java @@ -78,7 +78,7 @@ SecretClientBuilder getClientBuilder(HttpClient httpClient, String testTenantId, credential = new MockTokenCredential(); List customMatchers = new ArrayList<>(); customMatchers.add(new BodilessMatcher()); - customMatchers.add(new CustomMatcher().setExcludedHeaders(Collections.singletonList("Authorization"))); + customMatchers.add(new CustomMatcher().setExcludedHeaders(Arrays.asList("Authorization", "Accept"))); interceptorManager.addMatchers(customMatchers); } diff --git a/sdk/keyvault/azure-security-keyvault-secrets/tsp-location.yaml b/sdk/keyvault/azure-security-keyvault-secrets/tsp-location.yaml index a7133e413678..d2419e3c7751 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/tsp-location.yaml +++ b/sdk/keyvault/azure-security-keyvault-secrets/tsp-location.yaml @@ -1,6 +1,5 @@ -directory: specification/keyvault/Security.KeyVault.Secrets -commit: 396ab529763b7195ab089f58e2eefb011e1b290d +directory: specification/keyvault/data-plane/Secrets +commit: f6bd06be22baf3a18504ffef0f590230850953e5 repo: Azure/azure-rest-api-specs -cleanup: true additionalDirectories: -- specification/keyvault/Security.KeyVault.Common/ +- specification/keyvault/data-plane/Secrets/common