From 20a4d0896fae0fc8a902e46b55b1f13a139f08a5 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 19 Feb 2025 15:44:32 +0100 Subject: [PATCH] Use JSpecify to indicate nullness. Closes #902 --- pom.xml | 1 - .../authentication/AppRoleAuthentication.java | 5 +- .../AppRoleAuthenticationOptions.java | 18 ++-- .../authentication/AuthenticationSteps.java | 13 +-- .../AuthenticationStepsExecutor.java | 22 +++-- .../AuthenticationStepsOperator.java | 2 +- .../AzureMsiAuthentication.java | 13 ++- .../CachingVaultTokenSupplier.java | 4 +- .../LifecycleAwareSessionManager.java | 4 +- .../vault/authentication/LoginToken.java | 3 +- .../vault/authentication/LoginTokenUtil.java | 3 +- .../ReactiveLifecycleAwareSessionManager.java | 9 +- .../vault/authentication/ResponseUtil.java | 25 +++++ .../authentication/UnwrappingEndpoints.java | 1 + .../UsernamePasswordAuthentication.java | 1 + .../vault/client/ClientConfiguration.java | 14 ++- .../vault/client/RestTemplateBuilder.java | 2 + .../AbstractReactiveVaultConfiguration.java | 2 +- .../config/EnvironmentVaultConfiguration.java | 2 +- .../vault/core/KeyValueUtilities.java | 58 ++++++++--- .../vault/core/PropertyMapper.java | 6 +- .../core/ReactiveVaultKeyValue2Template.java | 2 +- .../vault/core/ReactiveVaultSysTemplate.java | 7 +- .../vault/core/RestOperationsCallback.java | 3 +- .../vault/core/VaultKeyValue1Template.java | 9 +- .../vault/core/VaultKeyValue2Accessor.java | 3 +- .../vault/core/VaultKeyValue2Template.java | 8 +- .../vault/core/VaultKeyValueAccessor.java | 29 +++--- .../core/VaultKeyValueMetadataTemplate.java | 4 +- .../vault/core/VaultKeyValueOperations.java | 3 +- .../vault/core/VaultOperations.java | 83 +++++++++++++--- .../vault/core/VaultPkiTemplate.java | 15 +-- .../vault/core/VaultSysTemplate.java | 34 ++++--- .../vault/core/VaultTemplate.java | 28 +++--- .../vault/core/VaultTokenTemplate.java | 10 +- .../vault/core/VaultTransformTemplate.java | 28 ++++-- .../vault/core/VaultTransitTemplate.java | 98 +++++++++---------- .../core/VaultVersionedKeyValueTemplate.java | 32 +++--- .../vault/core/VaultWrappingTemplate.java | 29 +++--- .../env/LeaseAwareVaultPropertySource.java | 4 +- .../vault/core/env/VaultPropertySource.java | 7 +- .../vault/core/lease/LeaseEndpoints.java | 11 ++- .../core/lease/SecretLeaseContainer.java | 9 +- .../vault/core/lease/domain/Lease.java | 20 +++- .../lease/event/SecretLeaseRotatedEvent.java | 1 + .../vault/core/util/KeyValueDelegate.java | 4 +- .../convert/DefaultVaultTypeMapper.java | 2 +- .../convert/MappingVaultConverter.java | 48 +++++---- .../repository/convert/SecretDocument.java | 6 +- .../convert/SecretDocumentAccessor.java | 5 +- .../security/VaultBytesKeyGenerator.java | 1 + .../vault/support/AbstractResult.java | 11 +-- .../vault/support/CertificateBundle.java | 2 +- .../vault/support/DurationParser.java | 9 +- .../vault/support/PemObject.java | 5 +- .../springframework/vault/support/Policy.java | 5 +- .../vault/support/SslConfiguration.java | 37 ++++--- .../vault/support/VaultMetadataResponse.java | 26 ++--- .../vault/support/VaultMount.java | 16 +-- .../vault/support/VaultResponseSupport.java | 38 ++----- .../vault/support/VaultToken.java | 6 +- .../vault/support/VaultTokenRequest.java | 15 ++- .../vault/support/VaultTokenResponse.java | 2 +- .../vault/support/VaultTransformContext.java | 3 +- .../vault/support/Versioned.java | 6 +- .../vault/support/WrappedMetadata.java | 2 +- .../core/lease/LeaseEndpointsUnitTests.java | 24 ++--- 67 files changed, 568 insertions(+), 390 deletions(-) diff --git a/pom.xml b/pom.xml index 35608a62..12a408b7 100644 --- a/pom.xml +++ b/pom.xml @@ -383,7 +383,6 @@ ${java.version} true - ${source.level} true diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthentication.java index a82dc976..195345ef 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthentication.java @@ -214,7 +214,7 @@ public class AppRoleAuthentication implements ClientAuthentication, Authenticati ResponseEntity entity = this.restOperations.exchange(getRoleIdIdPath(this.options), HttpMethod.GET, createHttpEntity(token), VaultResponse.class); - return (String) ResponseUtil.getRequiredData(entity).get("role_id"); + return (String) ResponseUtil.getRequiredValue(entity, "role_id"); } catch (HttpStatusCodeException e) { throw new VaultLoginException("Cannot get Role id using AppRole: %s" @@ -233,7 +233,7 @@ public class AppRoleAuthentication implements ClientAuthentication, Authenticati VaultResponse response = unwrappingEndpoints.unwrap(ResponseUtil.getRequiredBody(entity)); - return (String) response.getRequiredData().get("role_id"); + return (String) ResponseUtil.getRequiredValue(response, "role_id"); } catch (HttpStatusCodeException e) { throw new VaultLoginException("Cannot unwrap Role id using AppRole: %s" @@ -244,6 +244,7 @@ public class AppRoleAuthentication implements ClientAuthentication, Authenticati throw new IllegalArgumentException("Unknown RoleId configuration: " + roleId); } + @SuppressWarnings("NullAway") private String getSecretId(SecretId secretId) throws VaultLoginException { if (secretId instanceof Provided) { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthenticationOptions.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthenticationOptions.java index 306382fa..aa580e61 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthenticationOptions.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AppRoleAuthenticationOptions.java @@ -128,20 +128,15 @@ public class AppRoleAuthenticationOptions { private String path = DEFAULT_APPROLE_AUTHENTICATION_PATH; - @Nullable - private String providedRoleId; + private @Nullable String providedRoleId; - @Nullable - private RoleId roleId; + private @Nullable RoleId roleId; - @Nullable - private String providedSecretId; + private @Nullable String providedSecretId; - @Nullable - private SecretId secretId; + private @Nullable SecretId secretId; - @Nullable - private String appRole; + private @Nullable String appRole; private UnwrappingEndpoints unwrappingEndpoints = UnwrappingEndpoints.SysWrapping; @@ -250,6 +245,9 @@ public class AppRoleAuthenticationOptions { "AppRole authentication configured for pull mode. AppRole must not be null."); } + Assert.notNull(this.roleId, "RoleId must not be null"); + Assert.notNull(this.secretId, "SecretId must not be null"); + return new AppRoleAuthenticationOptions(this.path, this.roleId, this.secretId, this.appRole, this.unwrappingEndpoints); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationSteps.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationSteps.java index 89684f09..baccafc0 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationSteps.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationSteps.java @@ -299,7 +299,8 @@ public class AuthenticationSteps { @Nullable String uriTemplate; - String @Nullable[] urlVariables; + @Nullable + String[] urlVariables = new String[0]; @Nullable HttpEntity entity; @@ -379,14 +380,14 @@ public class AuthenticationSteps { this.uri = uri; } - private HttpRequestBuilder(HttpMethod method, @Nullable String uriTemplate, String @Nullable[] urlVariables) { + private HttpRequestBuilder(HttpMethod method, @Nullable String uriTemplate, @Nullable String[] urlVariables) { this.method = method; this.uriTemplate = uriTemplate; this.urlVariables = urlVariables; } private HttpRequestBuilder(HttpMethod method, @Nullable URI uri, @Nullable String uriTemplate, - String @Nullable[] urlVariables, @Nullable HttpEntity entity) { + @Nullable String[] urlVariables, @Nullable HttpEntity entity) { this.method = method; this.uri = uri; this.uriTemplate = uriTemplate; @@ -448,8 +449,7 @@ public class AuthenticationSteps { @Nullable final String uriTemplate; - - final String @Nullable[] urlVariables; + final @Nullable String[] urlVariables; @Nullable final HttpEntity entity; @@ -485,7 +485,8 @@ public class AuthenticationSteps { return this.uriTemplate; } - String @Nullable[] getUrlVariables() { + @Nullable + String[] getUrlVariables() { return this.urlVariables; } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java index 1d08dc8d..8e4a4956 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsExecutor.java @@ -92,7 +92,7 @@ public class AuthenticationStepsExecutor implements ClientAuthentication { } @SuppressWarnings({ "unchecked", "ConstantConditions" }) - private Object evaluate(Iterable> steps) { + private @Nullable Object evaluate(Iterable> steps) { Object state = null; @@ -108,14 +108,17 @@ public class AuthenticationStepsExecutor implements ClientAuthentication { } if (o instanceof MapStep) { + Assert.state(state != null, "No state available for MapStep"); state = doMapStep((MapStep) o, state); } if (o instanceof ZipStep) { + Assert.state(state != null, "No state available for ZipStep"); state = doZipStep((ZipStep) o, state); } if (o instanceof OnNextStep) { + Assert.state(state != null, "No state available for OnNextStep"); state = doOnNext((OnNextStep) o, state); } @@ -143,12 +146,11 @@ public class AuthenticationStepsExecutor implements ClientAuthentication { } @SuppressWarnings("ConstantConditions") - @Nullable - private Object doHttpRequest(HttpRequestNode step, @Nullable Object state) { + private @Nullable Object doHttpRequest(HttpRequestNode step, @Nullable Object state) { HttpRequest definition = step.getDefinition(); - if (definition.getUri() == null) { + if (definition.getUriTemplate() != null) { ResponseEntity exchange = this.restOperations.exchange(definition.getUriTemplate(), definition.getMethod(), getEntity(definition.getEntity(), state), definition.getResponseType(), @@ -156,11 +158,16 @@ public class AuthenticationStepsExecutor implements ClientAuthentication { return exchange.getBody(); } - ResponseEntity exchange = this.restOperations.exchange(definition.getUri(), definition.getMethod(), - getEntity(definition.getEntity(), state), definition.getResponseType()); - return exchange.getBody(); + if (definition.getUri() != null) { + ResponseEntity exchange = this.restOperations.exchange(definition.getUri(), definition.getMethod(), + getEntity(definition.getEntity(), state), definition.getResponseType()); + + return exchange.getBody(); + } + + return null; } static HttpEntity getEntity(@Nullable HttpEntity entity, @Nullable Object state) { @@ -185,6 +192,7 @@ public class AuthenticationStepsExecutor implements ClientAuthentication { return o.apply(state); } + @SuppressWarnings("NullAway") private Object doZipStep(ZipStep o, Object state) { Object result = evaluate(o.getRight()); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsOperator.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsOperator.java index d278b87f..6b0f2cfd 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsOperator.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AuthenticationStepsOperator.java @@ -155,7 +155,7 @@ public class AuthenticationStepsOperator implements VaultTokenSupplier { return state; } - @SuppressWarnings({"NullAway", "DataFlowIssue"}) + @SuppressWarnings({ "NullAway", "DataFlowIssue" }) private Mono doHttpRequest(HttpRequestNode step, Object state) { HttpRequest definition = step.getDefinition(); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AzureMsiAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AzureMsiAuthentication.java index 4bb4a95d..a813bc96 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/AzureMsiAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/AzureMsiAuthentication.java @@ -159,7 +159,7 @@ public class AzureMsiAuthentication implements ClientAuthentication, Authenticat VaultResponse response = this.vaultRestOperations .postForObject(AuthenticationUtil.getLoginPath(this.options.getPath()), login, VaultResponse.class); - Assert.state(response != null && response.getAuth() != null, "Auth field must not be null"); + Assert.state(response != null, "Auth field must not be null"); if (logger.isDebugEnabled()) { logger.debug("Login successful using Azure authentication"); @@ -185,6 +185,7 @@ public class AzureMsiAuthentication implements ClientAuthentication, Authenticat return loginBody; } + @SuppressWarnings({ "NullAway", "rawtypes" }) private String getAccessToken() { ResponseEntity response = this.azureMetadataRestOperations @@ -200,6 +201,7 @@ public class AzureMsiAuthentication implements ClientAuthentication, Authenticat return vmEnvironment != null ? vmEnvironment : fetchAzureVmEnvironment(); } + @SuppressWarnings({ "unchecked", "rawtypes" }) private AzureVmEnvironment fetchAzureVmEnvironment() { ResponseEntity response = this.azureMetadataRestOperations @@ -208,16 +210,23 @@ public class AzureMsiAuthentication implements ClientAuthentication, Authenticat return toAzureVmEnvironment(ResponseUtil.getRequiredBody(response)); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) private static AzureVmEnvironment toAzureVmEnvironment(Map instanceMetadata) { Map compute = (Map) instanceMetadata.get("compute"); + Assert.notNull(compute, "Metadata does not contain compute"); + String subscriptionId = compute.get("subscriptionId"); String resourceGroupName = compute.get("resourceGroupName"); String vmName = compute.get("name"); String vmScaleSetName = compute.get("vmScaleSetName"); + Assert.notNull(subscriptionId, "Metadata does not contain subscriptionId"); + Assert.notNull(resourceGroupName, "Metadata does not contain resourceGroupName"); + Assert.notNull(vmName, "Metadata does not contain name"); + Assert.notNull(vmScaleSetName, "Metadata does not contain vmScaleSetName"); + return new AzureVmEnvironment(subscriptionId, resourceGroupName, vmName, vmScaleSetName); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CachingVaultTokenSupplier.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CachingVaultTokenSupplier.java index fa611da2..dcc9dac9 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/CachingVaultTokenSupplier.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/CachingVaultTokenSupplier.java @@ -18,6 +18,7 @@ package org.springframework.vault.authentication; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; +import org.jspecify.annotations.NonNull; import reactor.core.publisher.Mono; import org.springframework.vault.VaultException; @@ -38,7 +39,7 @@ public class CachingVaultTokenSupplier implements VaultTokenSupplier, ReactiveSe private final VaultTokenSupplier clientAuthentication; - private final AtomicReference> tokenRef = new AtomicReference<>(EMPTY); + private final AtomicReference<@NonNull Mono> tokenRef = new AtomicReference<>(EMPTY); private CachingVaultTokenSupplier(VaultTokenSupplier clientAuthentication) { this.clientAuthentication = clientAuthentication; @@ -56,6 +57,7 @@ public class CachingVaultTokenSupplier implements VaultTokenSupplier, ReactiveSe } @Override + @SuppressWarnings("NullAway") public Mono getVaultToken() throws VaultException { if (Objects.equals(this.tokenRef.get(), EMPTY)) { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java index 0794e03a..b6210aa7 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LifecycleAwareSessionManager.java @@ -239,7 +239,9 @@ public class LifecycleAwareSessionManager extends LifecycleAwareSessionManagerSu VaultResponse vaultResponse = this.restOperations.postForObject("auth/token/renew-self", new HttpEntity<>(VaultHttpHeaders.from(wrapper.token)), VaultResponse.class); - LoginToken renewed = LoginTokenUtil.from(vaultResponse.getRequiredAuth()); + Assert.notNull(vaultResponse, "VaultResponse must not be null"); + + LoginToken renewed = LoginTokenUtil.from(vaultResponse.getAuth()); if (isExpired(renewed)) { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginToken.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginToken.java index f6cc6daa..4350d786 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginToken.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginToken.java @@ -206,8 +206,7 @@ public class LoginToken extends VaultToken { */ public static class LoginTokenBuilder { - - private char @Nullable[] token; + private char @Nullable [] token; private boolean renewable; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java index f4eaaa4c..0ae4c9c5 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LoginTokenUtil.java @@ -40,8 +40,8 @@ final class LoginTokenUtil { static LoginToken from(Map auth) { Assert.notNull(auth, "Authentication must not be null"); - String token = (String) auth.get("client_token"); + Assert.notNull(token, "Authentication must contain 'client_token' key"); return from(token.toCharArray(), auth); } @@ -52,6 +52,7 @@ final class LoginTokenUtil { * @return the {@link LoginToken} * @since 2.0 */ + @SuppressWarnings("NullAway") static LoginToken from(char[] token, Map auth) { Assert.notNull(auth, "Authentication must not be null"); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ReactiveLifecycleAwareSessionManager.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ReactiveLifecycleAwareSessionManager.java index d15f1524..e638b6ca 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ReactiveLifecycleAwareSessionManager.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ReactiveLifecycleAwareSessionManager.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; +import org.jspecify.annotations.NonNull; import reactor.core.publisher.Mono; import org.springframework.beans.factory.DisposableBean; @@ -91,7 +92,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM * The token state: Contains the currently valid token that identifies the Vault * session. */ - private volatile AtomicReference> token = new AtomicReference<>(EMPTY); + private final AtomicReference<@NonNull Mono> token = new AtomicReference<>(EMPTY); /** * Create a {@link ReactiveLifecycleAwareSessionManager} given @@ -136,6 +137,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM } @Override + @SuppressWarnings("NullAway") public void destroy() { Mono tokenMono = this.token.get(); @@ -149,6 +151,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM * @return a mono emitting completion upon successful revocation. * @since 3.0.2 */ + @SuppressWarnings("NullAway") public Mono revoke() { return doRevoke(this.token.get()).doOnSuccess(unused -> this.token.set(EMPTY)); } @@ -216,6 +219,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM * obtained. {@link Mono#empty()} if a new the token expired or * {@link Mono#error(Throwable)} if refresh failed. */ + @SuppressWarnings("NullAway") public Mono renewToken() { this.logger.info("Renewing token"); @@ -268,7 +272,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM .doOnSubscribe(ignore -> multicastEvent(new BeforeLoginTokenRenewedEvent(tokenWrapper.getToken()))) .handle((response, sink) -> { - LoginToken renewed = LoginTokenUtil.from(response.getRequiredAuth()); + LoginToken renewed = LoginTokenUtil.from(response.getAuth()); if (!isExpired(renewed)) { sink.next(new TokenWrapper(renewed, tokenWrapper.revocable)); @@ -301,6 +305,7 @@ public class ReactiveLifecycleAwareSessionManager extends LifecycleAwareSessionM } @Override + @SuppressWarnings("NullAway") public Mono getVaultToken() throws VaultException { Mono tokenWrapper = this.token.get(); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ResponseUtil.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ResponseUtil.java index f0141c93..ab1e7579 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/ResponseUtil.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/ResponseUtil.java @@ -15,12 +15,16 @@ */ package org.springframework.vault.authentication; +import java.util.Map; + import org.jspecify.annotations.Nullable; import org.springframework.http.ResponseEntity; import org.springframework.vault.support.VaultResponseSupport; /** + * Utilities to extract required responses from {@link ResponseEntity}. + * * @author Mark Paluch */ class ResponseUtil { @@ -49,4 +53,25 @@ class ResponseUtil { return response.getRequiredData(); } + + public static Object getRequiredValue(ResponseEntity>> response, + String key) { + return getRequiredValue(getRequiredBody(response), key); + } + + public static Object getRequiredValue(@Nullable VaultResponseSupport> response, String key) { + + if (response == null) { + throw new IllegalStateException("Expected non-null response body"); + } + + Object value = response.getRequiredData().get(key); + + if (value == null) { + throw new IllegalStateException(String.format("Key '%s' not found in response", key)); + } + + return value; + } + } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/UnwrappingEndpoints.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/UnwrappingEndpoints.java index 69f273a9..84dc241e 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/UnwrappingEndpoints.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/UnwrappingEndpoints.java @@ -26,6 +26,7 @@ import org.springframework.vault.support.VaultResponse; * @author Mark Paluch * @since 2.2 */ +@SuppressWarnings("NullAway") public enum UnwrappingEndpoints { /** diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthentication.java index b83af305..fb68c08c 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthentication.java @@ -90,6 +90,7 @@ public class UsernamePasswordAuthentication implements ClientAuthentication, Aut return createAuthenticationSteps(this.options); } + @SuppressWarnings("NullAway") private VaultToken createTokenUsingUsernamePasswordAuthentication() { try { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientConfiguration.java index 86ff090f..c7fe74b2 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientConfiguration.java @@ -69,7 +69,7 @@ class ClientConfiguration { } static SSLContext getSSLContext(SslConfiguration.KeyStoreConfiguration keyStoreConfiguration, - SslConfiguration.KeyConfiguration keyConfiguration, TrustManager @Nullable[] trustManagers) + SslConfiguration.KeyConfiguration keyConfiguration, TrustManager @Nullable [] trustManagers) throws GeneralSecurityException, IOException { KeyManager[] keyManagers = keyStoreConfiguration.isPresent() @@ -113,9 +113,8 @@ class ClientConfiguration { return keyStore; } - - static TrustManager @Nullable[] getTrustManagers(SslConfiguration sslConfiguration) - throws GeneralSecurityException, IOException { + static TrustManager @Nullable [] getTrustManagers(SslConfiguration sslConfiguration) + throws GeneralSecurityException, IOException { return sslConfiguration.getTrustStoreConfiguration().isPresent() ? createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()).getTrustManagers() : null; @@ -150,8 +149,7 @@ class ClientConfiguration { logger.debug("Loading keystore from %s".formatted(keyStoreConfiguration.getResource())); } - try (InputStream inputStream = keyStoreConfiguration.getResource() - .getInputStream()) { + try (InputStream inputStream = keyStoreConfiguration.getResource().getInputStream()) { if (SslConfiguration.PEM_KEYSTORE_TYPE.equalsIgnoreCase(keyStoreConfiguration.getStoreType())) { @@ -408,11 +406,11 @@ class ClientConfiguration { } @Override - public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { + public @Nullable String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { return this.keyConfiguration.getKeyAlias(); } - public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { + public @Nullable String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { return this.keyConfiguration.getKeyAlias(); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateBuilder.java b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateBuilder.java index 4e99d12a..0efd89ab 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateBuilder.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/RestTemplateBuilder.java @@ -219,6 +219,8 @@ public class RestTemplateBuilder { */ protected RestTemplate createTemplate() { + Assert.notNull(this.endpointProvider, "VaultEndpointProvider must not be null"); + ClientHttpRequestFactory requestFactory = this.requestFactory.get(); LinkedHashMap defaultHeaders = new LinkedHashMap<>(this.defaultHeaders); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java index c7e4f38a..92e0ae51 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/AbstractReactiveVaultConfiguration.java @@ -208,7 +208,7 @@ public abstract class AbstractReactiveVaultConfiguration extends AbstractVaultCo throw new IllegalStateException(("Cannot construct VaultTokenSupplier from %s. " + "ClientAuthentication must implement AuthenticationStepsFactory or be TokenAuthentication") - .formatted(clientAuthentication)); + .formatted(clientAuthentication)); } /** diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java index b34bd261..72375508 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java @@ -425,7 +425,7 @@ public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration im private Resource getResource(String key) { String value = getProperty(key); - return value != null ? this.applicationContext.getResource(value) : null; + return value != null && this.applicationContext != null ? this.applicationContext.getResource(value) : null; } enum AuthenticationMethod { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/KeyValueUtilities.java b/spring-vault-core/src/main/java/org/springframework/vault/core/KeyValueUtilities.java index c254423e..c3174a11 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/KeyValueUtilities.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/KeyValueUtilities.java @@ -35,7 +35,7 @@ class KeyValueUtilities { static Metadata getMetadata(Map responseMetadata) { MetadataBuilder builder = Metadata.builder(); - TemporalAccessor created_time = getDate(responseMetadata, "created_time"); + TemporalAccessor created_time = getRequiredDate(responseMetadata, "created_time"); TemporalAccessor deletion_time = getDate(responseMetadata, "deletion_time"); builder.createdAt(Instant.from(created_time)); @@ -51,13 +51,12 @@ class KeyValueUtilities { builder.customMetadata((Map) responseMetadata.get("custom_metadata")); Integer version = (Integer) responseMetadata.get("version"); - builder.version(Version.from(version)); + builder.version(version != null ? Version.from(version) : Version.unversioned()); return builder.build(); } - @Nullable - private static TemporalAccessor getDate(Map responseMetadata, String key) { + private static @Nullable TemporalAccessor getDate(Map responseMetadata, String key) { String date = (String) responseMetadata.getOrDefault(key, ""); if (StringUtils.hasText(date)) { @@ -66,6 +65,17 @@ class KeyValueUtilities { return null; } + private static TemporalAccessor getRequiredDate(Map responseMetadata, String key) { + + TemporalAccessor date = getDate(responseMetadata, key); + + if (date == null) { + throw new IllegalArgumentException("Date for key '" + key + "' is null"); + } + + return date; + } + @SuppressWarnings({ "ConstantConditions", "unchecked", "rawtypes" }) static VaultMetadataResponse fromMap(Map metadataResponse) { @@ -73,22 +83,37 @@ class KeyValueUtilities { return VaultMetadataResponse.builder() .casRequired(Boolean.parseBoolean(String.valueOf(metadataResponse.get("cas_required")))) - .createdTime(toInstant((String) metadataResponse.get("created_time"))) + .createdTime(toRequiredInstant((String) metadataResponse.get("created_time"))) .currentVersion(Integer.parseInt(String.valueOf(metadataResponse.get("current_version")))) .deleteVersionAfter(duration) .maxVersions(Integer.parseInt(String.valueOf(metadataResponse.get("max_versions")))) .oldestVersion(Integer.parseInt(String.valueOf(metadataResponse.get("oldest_version")))) - .updatedTime(toInstant((String) metadataResponse.get("updated_time"))) + .updatedTime(toRequiredInstant((String) metadataResponse.get("updated_time"))) .versions(buildVersions((Map) metadataResponse.get("versions"))) .build(); } @Nullable - static Instant toInstant(String date) { + static Instant toInstant(@Nullable String date) { return StringUtils.hasText(date) ? Instant.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(date)) : null; } - private static List buildVersions(Map> versions) { + static Instant toRequiredInstant(@Nullable String date) { + + Instant instant = toInstant(date); + + if (instant == null) { + throw new IllegalArgumentException("Date is null"); + } + + return instant; + } + + private static List buildVersions(@Nullable Map> versions) { + + if (versions == null) { + return Collections.emptyList(); + } return versions.entrySet() .stream() @@ -98,17 +123,18 @@ class KeyValueUtilities { private static Versioned.Metadata buildVersion(String version, Map versionData) { - Instant createdTime = toInstant((String) versionData.get("created_time")); + Instant createdTime = toRequiredInstant((String) versionData.get("created_time")); Instant deletionTime = toInstant((String) versionData.get("deletion_time")); - boolean destroyed = (Boolean) versionData.get("destroyed"); + Boolean destroyed = (Boolean) versionData.get("destroyed"); Versioned.Version kvVersion = Versioned.Version.from(Integer.parseInt(version)); - return Versioned.Metadata.builder() - .createdAt(createdTime) - .deletedAt(deletionTime) - .destroyed(destroyed) - .version(kvVersion) - .build(); + MetadataBuilder builder = Metadata.builder().createdAt(createdTime).deletedAt(deletionTime).version(kvVersion); + + if (destroyed != null) { + builder.destroyed(destroyed); + } + + return builder.build(); } static Map createPatchRequest(Map patch, Map previous, diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/PropertyMapper.java b/spring-vault-core/src/main/java/org/springframework/vault/core/PropertyMapper.java index ebdcb28f..3f6e6024 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/PropertyMapper.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/PropertyMapper.java @@ -84,7 +84,7 @@ class PropertyMapper { * @return a {@link Source} that can be used to complete the mapping * @see #from(Object) */ - public Source from(Supplier supplier) { + public Source from(Supplier<@Nullable T> supplier) { Assert.notNull(supplier, "Supplier must not be null"); Source source = getSource(supplier); if (this.sourceOperator != null) { @@ -332,10 +332,10 @@ class PropertyMapper { /** * Supplier that will catch and ignore any {@link NullPointerException}. */ - private record NullPointerExceptionSafeSupplier(Supplier supplier) implements Supplier { + private record NullPointerExceptionSafeSupplier(Supplier supplier) implements Supplier<@Nullable T> { @Override - public T get() { + public @Nullable T get() { try { return this.supplier.get(); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultKeyValue2Template.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultKeyValue2Template.java index c505c740..e056d06c 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultKeyValue2Template.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultKeyValue2Template.java @@ -86,7 +86,7 @@ class ReactiveVaultKeyValue2Template extends ReactiveVaultKeyValue2Accessor impl return get(path).filter(it -> it.getData() != null) .switchIfEmpty(Mono.error(new SecretNotFoundException( - "No data found at %s; patch only works on existing data".formatted(createDataPath(path)), + "No data found at '%s'; patch only works on existing data".formatted(createDataPath(path)), createLogicalPath(path)))) .flatMap(readResponse -> { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultSysTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultSysTemplate.java index 9d01839b..51ad4a78 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultSysTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/ReactiveVaultSysTemplate.java @@ -47,6 +47,7 @@ public class ReactiveVaultSysTemplate implements ReactiveVaultSysOperations { } @Override + @SuppressWarnings("NullAway") public Mono isInitialized() { return this.vaultOperations.doWithSession(webClient -> { @@ -54,11 +55,13 @@ public class ReactiveVaultSysTemplate implements ReactiveVaultSysOperations { .uri("sys/init") .header(VaultHttpHeaders.VAULT_NAMESPACE, "") .exchangeToMono(clientResponse -> clientResponse.toEntity(Map.class)) + .filter(HttpEntity::hasBody) .map(it -> (Boolean) it.getBody().get("initialized")); }); } @Override + @SuppressWarnings("NullAway") public Mono health() { return this.vaultOperations.doWithVault(webClient -> { @@ -67,7 +70,9 @@ public class ReactiveVaultSysTemplate implements ReactiveVaultSysOperations { .uri("sys/health") .header(VaultHttpHeaders.VAULT_NAMESPACE, "") .exchangeToMono(clientResponse -> { - return clientResponse.toEntity(VaultSysTemplate.VaultHealthImpl.class).map(HttpEntity::getBody); + return clientResponse.toEntity(VaultSysTemplate.VaultHealthImpl.class) + .filter(HttpEntity::hasBody) + .map(HttpEntity::getBody); }); }); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/RestOperationsCallback.java b/spring-vault-core/src/main/java/org/springframework/vault/core/RestOperationsCallback.java index 1627c605..02557829 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/RestOperationsCallback.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/RestOperationsCallback.java @@ -25,14 +25,13 @@ import org.springframework.web.client.RestOperations; * @author Mark Paluch */ @FunctionalInterface -public interface RestOperationsCallback { +public interface RestOperationsCallback { /** * Callback method. * @param restOperations restOperations to use, must not be {@literal null}. * @return a result object or null if none. */ - @Nullable T doWithRestOperations(RestOperations restOperations); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue1Template.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue1Template.java index 495f58df..4de6f78d 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue1Template.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue1Template.java @@ -54,15 +54,13 @@ class VaultKeyValue1Template extends VaultKeyValueAccessor implements VaultKeyVa this.path = path; } - @Nullable @Override - public List list(String path) { + public @Nullable List list(String path) { return this.vaultOperations.list(createDataPath(path)); } - @Nullable @Override - public VaultResponse get(String path) { + public @Nullable VaultResponse get(String path) { Assert.hasText(path, "Path must not be empty"); @@ -76,10 +74,9 @@ class VaultKeyValue1Template extends VaultKeyValueAccessor implements VaultKeyVa }); } - @Nullable @Override @SuppressWarnings("unchecked") - public VaultResponseSupport get(String path, Class responseType) { + public @Nullable VaultResponseSupport get(String path, Class responseType) { Assert.hasText(path, "Path must not be empty"); Assert.notNull(responseType, "Response type must not be null"); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue2Accessor.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue2Accessor.java index 7ca869c5..60313a73 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue2Accessor.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue2Accessor.java @@ -48,10 +48,9 @@ abstract class VaultKeyValue2Accessor extends VaultKeyValueAccessor { this.path = path; } - @Nullable @Override @SuppressWarnings("unchecked") - public List list(String path) { + public @Nullable List list(String path) { VaultListResponse read = doRead(restOperations -> { return restOperations.exchange( diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue2Template.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue2Template.java index 05678b4a..4c05db0c 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue2Template.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValue2Template.java @@ -48,9 +48,8 @@ class VaultKeyValue2Template extends VaultKeyValue2Accessor implements VaultKeyV this.path = path; } - @Nullable @Override - public VaultResponse get(String path) { + public @Nullable VaultResponse get(String path) { Assert.hasText(path, "Path must not be empty"); @@ -64,10 +63,9 @@ class VaultKeyValue2Template extends VaultKeyValue2Accessor implements VaultKeyV }); } - @Nullable @Override @SuppressWarnings({ "unchecked", "rawtypes" }) - public VaultResponseSupport get(String path, Class responseType) { + public @Nullable VaultResponseSupport get(String path, Class responseType) { Assert.hasText(path, "Path must not be empty"); Assert.notNull(responseType, "Response type must not be null"); @@ -90,7 +88,7 @@ class VaultKeyValue2Template extends VaultKeyValue2Accessor implements VaultKeyV VaultResponse readResponse = get(path); if (readResponse == null || readResponse.getData() == null) { throw new SecretNotFoundException( - "No data found at %s; patch only works on existing data".formatted(createDataPath(path)), + "No data found at '%s'; patch only works on existing data".formatted(createDataPath(path)), "%s/%s".formatted(this.path, path)); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueAccessor.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueAccessor.java index e511d979..5f1dd2e2 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueAccessor.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueAccessor.java @@ -16,6 +16,7 @@ package org.springframework.vault.core; import java.io.IOException; +import java.util.Objects; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; @@ -75,11 +76,12 @@ abstract class VaultKeyValueAccessor implements VaultKeyValueOperationsSupport { } @Override + @SuppressWarnings("NullAway") public void delete(String path) { Assert.hasText(path, "Path must not be empty"); - this.vaultOperations.doWithSession((restOperations -> { + this.vaultOperations.doWithSession((RestOperationsCallback<@Nullable Void>) (restOperations -> { restOperations.exchange(createDataPath(path), HttpMethod.DELETE, null, Void.class); return null; @@ -97,8 +99,8 @@ abstract class VaultKeyValueAccessor implements VaultKeyValueOperationsSupport { * @param return type. Value is created by the {@code mappingFunction}. * @return mapped value. */ - @Nullable - T doRead(String path, Class deserializeAs, BiFunction, I, T> mappingFunction) { + @Nullable T doRead(String path, Class deserializeAs, + BiFunction, I, T> mappingFunction) { ParameterizedTypeReference> ref = VaultResponses .getTypeReference(JsonNode.class); @@ -125,8 +127,7 @@ abstract class VaultKeyValueAccessor implements VaultKeyValueOperationsSupport { * @param typeReference must not be {@literal null} * @return mapped value. */ - @Nullable - T doRead(String path, ParameterizedTypeReference typeReference) { + @Nullable T doRead(String path, ParameterizedTypeReference typeReference) { return doRead((restOperations) -> { return restOperations.exchange(path, HttpMethod.GET, null, typeReference); @@ -156,10 +157,10 @@ abstract class VaultKeyValueAccessor implements VaultKeyValueOperationsSupport { * @param callback must not be {@literal null}. * @return can be {@literal null}. */ - @Nullable - T doRead(Function> callback) { + @SuppressWarnings("NullAway") + @Nullable T doRead(Function> callback) { - return this.vaultOperations.doWithSession((restOperations) -> { + return this.vaultOperations.doWithSession((RestOperationsCallback<@Nullable T>) (restOperations) -> { try { return callback.apply(restOperations).getBody(); @@ -182,16 +183,18 @@ abstract class VaultKeyValueAccessor implements VaultKeyValueOperationsSupport { * @return the response of this write action. */ @Nullable + @SuppressWarnings("NullAway") VaultResponse doWrite(String path, Object body) { Assert.hasText(path, "Path must not be empty"); try { - return this.vaultOperations.doWithSession((restOperations) -> { - return restOperations.exchange(path, HttpMethod.POST, new HttpEntity<>(body), VaultResponse.class) - .getBody(); - }); + return this.vaultOperations + .doWithSession((RestOperationsCallback<@Nullable VaultResponse>) (restOperations) -> { + return restOperations.exchange(path, HttpMethod.POST, new HttpEntity<>(body), VaultResponse.class) + .getBody(); + }); } catch (HttpStatusCodeException e) { throw VaultResponses.buildException(e, path); @@ -229,7 +232,7 @@ abstract class VaultKeyValueAccessor implements VaultKeyValueOperationsSupport { return Optional.empty(); }); - return mapper.orElseGet(ObjectMapper::new); + return Objects.requireNonNull(mapper).orElseGet(ObjectMapper::new); } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueMetadataTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueMetadataTemplate.java index e8f325df..cf494670 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueMetadataTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueMetadataTemplate.java @@ -17,6 +17,8 @@ package org.springframework.vault.core; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.vault.support.VaultMetadataRequest; import org.springframework.vault.support.VaultMetadataResponse; @@ -46,7 +48,7 @@ class VaultKeyValueMetadataTemplate implements VaultKeyValueMetadataOperations { @Override @SuppressWarnings({ "rawtypes", "unchecked" }) - public VaultMetadataResponse get(String path) { + public @Nullable VaultMetadataResponse get(String path) { VaultResponseSupport response = this.vaultOperations.read(getPath(path), Map.class); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperations.java index 17af0f24..425d1942 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperations.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperations.java @@ -54,8 +54,7 @@ public interface VaultKeyValueOperations extends VaultKeyValueOperationsSupport * @param responseType must not be {@literal null}. * @return the data. May be {@literal null} if the path does not exist. */ - @Nullable - VaultResponseSupport get(String path, Class responseType); + @Nullable VaultResponseSupport get(String path, Class responseType); /** * Update the secret at {@code path} without removing the existing secrets. Requires a diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultOperations.java index c2e6776b..3cdd507e 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultOperations.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultOperations.java @@ -122,8 +122,8 @@ public interface VaultOperations { VaultWrappingOperations opsForWrapping(); /** - * Read from a Vault path. Reading data using this method is suitable for API - * calls/secret backends that do not require a request body. + * Read ({@code GET)} from a Vault path. Reading data using this method is suitable + * for API calls/secret backends that do not require a request body. * @param path must not be {@literal null}. * @return the data. May be {@literal null} if the path does not exist. */ @@ -131,14 +131,52 @@ public interface VaultOperations { VaultResponse read(String path); /** - * Read from a secret backend. Reading data using this method is suitable for secret - * backends that do not require a request body. + * Read ({@code GET)} from a Vault path. Reading data using this method is suitable + * for API calls/secret backends that do not require a request body. + * @param path must not be {@literal null}. + * @return the data. + * @throws SecretNotFoundException if the path does not exist. + * @since 4.0 + */ + default VaultResponse readRequired(String path) throws SecretNotFoundException { + + VaultResponse response = read(path); + + if (response == null) { + throw new SecretNotFoundException("No data found at '%s'".formatted(path), path); + } + + return response; + } + + /** + * Read ({@code GET)} from a secret backend. Reading data using this method is + * suitable for secret backends that do not require a request body. * @param path must not be {@literal null}. * @param responseType must not be {@literal null}. * @return the data. May be {@literal null} if the path does not exist. */ - @Nullable - VaultResponseSupport read(String path, Class responseType); + VaultResponseSupport read(String path, Class responseType); + + /** + * Read ({@code GET)} from a secret backend. Reading data using this method is + * suitable for secret backends that do not require a request body. + * @param path must not be {@literal null}. + * @param responseType must not be {@literal null}. + * @return the data. + * @throws SecretNotFoundException if the path does not exist. + * @since 4.0 + */ + default VaultResponseSupport readRequired(String path, Class responseType) { + + VaultResponseSupport response = read(path, responseType); + + if (response == null) { + throw new SecretNotFoundException("No data found at '%s'".formatted(path), path); + } + + return response; + } /** * Enumerate keys from a Vault path. @@ -149,7 +187,7 @@ public interface VaultOperations { List list(String path); /** - * Write to a Vault path. + * Write ({@code POST)} to a Vault path. * @param path must not be {@literal null}. * @return the response, may be {@literal null}. * @since 2.0 @@ -160,7 +198,7 @@ public interface VaultOperations { } /** - * Write to a Vault path. + * Write ({@code POST)} to a Vault path. * @param path must not be {@literal null}. * @param body the body, may be {@literal null} if absent. * @return the response, may be {@literal null}. @@ -168,6 +206,27 @@ public interface VaultOperations { @Nullable VaultResponse write(String path, @Nullable Object body); + /** + * Invoke an operation on a Vault path, typically a {@code POST} request along with an + * optional request body expecing a response. + * @param path must not be {@literal null}. + * @param body the body, may be {@literal null} if absent. + * @return the response. + * @throws IllegalStateException if the operation returns without returning a + * response. + * @since 4.0 + */ + default VaultResponse invoke(String path, @Nullable Object body) { + + VaultResponse response = write(path, body); + + if (response == null) { + throw new IllegalStateException("No response received from Vault, writing to '%s'".formatted(path)); + } + + return response; + } + /** * Delete a path. * @param path must not be {@literal null}. @@ -185,8 +244,8 @@ public interface VaultOperations { * @throws RestClientException exceptions from * {@link org.springframework.web.client.RestOperations}. */ - @Nullable - T doWithVault(RestOperationsCallback clientCallback) throws VaultException, RestClientException; + T doWithVault(RestOperationsCallback clientCallback) + throws VaultException, RestClientException; /** * Executes a Vault {@link RestOperationsCallback}. Allows to interact with Vault in @@ -198,7 +257,7 @@ public interface VaultOperations { * @throws RestClientException exceptions from * {@link org.springframework.web.client.RestOperations}. */ - @Nullable - T doWithSession(RestOperationsCallback sessionCallback) throws VaultException, RestClientException; + T doWithSession(RestOperationsCallback sessionCallback) + throws VaultException, RestClientException; } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiTemplate.java index 47a97bf0..dcae4133 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultPkiTemplate.java @@ -24,6 +24,8 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -87,12 +89,13 @@ public class VaultPkiTemplate implements VaultPkiOperations { return requestCertificate(roleName, "{path}/sign/{roleName}", body, VaultSignCertificateRequestResponse.class); } + @SuppressWarnings("NullAway") private T requestCertificate(String roleName, String requestPath, Map request, Class responseType) { request.putIfAbsent("format", "der"); - T response = this.vaultOperations.doWithSession(restOperations -> { + return this.vaultOperations.doWithSession(restOperations -> { try { return restOperations.postForObject(requestPath, request, responseType, this.path, roleName); @@ -101,18 +104,15 @@ public class VaultPkiTemplate implements VaultPkiOperations { throw VaultResponses.buildException(e); } }); - - Assert.state(response != null, "VaultCertificateResponse must not be null"); - - return response; } @Override + @SuppressWarnings("NullAway") public void revoke(String serialNumber) throws VaultException { Assert.hasText(serialNumber, "Serial number must not be null or empty"); - this.vaultOperations.doWithSession(restOperations -> { + this.vaultOperations.doWithSession((RestOperationsCallback<@Nullable Void>) restOperations -> { try { restOperations.postForObject("{path}/revoke", Collections.singletonMap("serial_number", serialNumber), @@ -127,6 +127,7 @@ public class VaultPkiTemplate implements VaultPkiOperations { } @Override + @SuppressWarnings({ "NullAway", "DataFlowIssue" }) public InputStream getCrl(Encoding encoding) throws VaultException { Assert.notNull(encoding, "Encoding must not be null"); @@ -150,6 +151,7 @@ public class VaultPkiTemplate implements VaultPkiOperations { } @Override + @SuppressWarnings("NullAway") public VaultIssuerCertificateRequestResponse getIssuerCertificate(String issuer) throws VaultException { Assert.hasText(issuer, "Issuer must not be empty"); @@ -167,6 +169,7 @@ public class VaultPkiTemplate implements VaultPkiOperations { } @Override + @SuppressWarnings({ "NullAway", "DataFlowIssue" }) public InputStream getIssuerCertificate(String issuer, Encoding encoding) throws VaultException { Assert.hasText(issuer, "Issuer must not be empty"); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultSysTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultSysTemplate.java index 344c5602..03e619fb 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultSysTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultSysTemplate.java @@ -96,7 +96,7 @@ public class VaultSysTemplate implements VaultSysOperations { } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) public boolean isInitialized() { return requireResponse(this.vaultOperations.doWithSession(restOperations -> { @@ -107,7 +107,8 @@ public class VaultSysTemplate implements VaultSysOperations { Assert.state(body.getBody() != null, "Initialization response must not be null"); - return body.getBody().get("initialized"); + Boolean initialized = body.getBody().get("initialized"); + return initialized != null && initialized.booleanValue(); } catch (HttpStatusCodeException e) { throw VaultResponses.buildException(e); @@ -209,16 +210,16 @@ public class VaultSysTemplate implements VaultSysOperations { @SuppressWarnings("unchecked") public List getPolicyNames() throws VaultException { return requireResponse( - (List) this.vaultOperations.read("sys/policy").getRequiredData().get("policies")); + (List) this.vaultOperations.readRequired("sys/policy").getRequiredData().get("policies")); } - @Nullable @Override - public Policy getPolicy(String name) throws VaultException { + @SuppressWarnings("NullAway") + public @Nullable Policy getPolicy(String name) throws VaultException { Assert.hasText(name, "Name must not be null or empty"); - return this.vaultOperations.doWithSession(restOperations -> { + return this.vaultOperations.doWithSession((RestOperationsCallback<@Nullable Policy>) restOperations -> { ResponseEntity response; @@ -234,7 +235,13 @@ public class VaultSysTemplate implements VaultSysOperations { throw e; } - String rules = (String) response.getBody().getRequiredData().get("rules"); + VaultResponse body = response.getBody(); + + if (body == null) { + return null; + } + + String rules = (String) body.getRequiredData().get("rules"); if (ObjectUtils.isEmpty(rules)) { return Policy.empty(); @@ -249,6 +256,7 @@ public class VaultSysTemplate implements VaultSysOperations { } @Override + @SuppressWarnings("NullAway") public void createOrUpdatePolicy(String name, Policy policy) throws VaultException { Assert.hasText(name, "Name must not be null or empty"); @@ -263,7 +271,7 @@ public class VaultSysTemplate implements VaultSysOperations { throw new VaultException("Cannot serialize policy to JSON", e); } - this.vaultOperations.doWithSession(restOperations -> { + this.vaultOperations.doWithSession((RestOperationsCallback<@Nullable Void>) restOperations -> { restOperations.exchange("sys/policy/{name}", HttpMethod.PUT, new HttpEntity<>(Collections.singletonMap("rules", rules)), VaultResponse.class, name); @@ -296,12 +304,12 @@ public class VaultSysTemplate implements VaultSysOperations { @Override public VaultUnsealStatus doWithRestOperations(RestOperations restOperations) { - return restOperations.getForObject("sys/seal-status", VaultUnsealStatusImpl.class); + return requireResponse(restOperations.getForObject("sys/seal-status", VaultUnsealStatusImpl.class)); } } - private static class Seal implements RestOperationsCallback { + private static class Seal implements RestOperationsCallback<@Nullable Void> { @Override public Void doWithRestOperations(RestOperations restOperations) { @@ -335,14 +343,14 @@ public class VaultSysTemplate implements VaultSysOperations { private static class VaultMountsResponse extends VaultResponseSupport> { - private Map topLevelMounts = new HashMap<>(); + private final Map topLevelMounts = new HashMap<>(); @JsonIgnore public Map getTopLevelMounts() { return this.topLevelMounts; } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "NullAway", "rawtypes" }) @JsonAnySetter public void set(String name, Object value) { @@ -386,7 +394,7 @@ public class VaultSysTemplate implements VaultSysOperations { try { ResponseEntity healthResponse = restOperations.exchange("sys/health", HttpMethod.GET, emptyNamespace(null), VaultHealthImpl.class); - return healthResponse.getBody(); + return requireResponse(healthResponse.getBody()); } catch (RestClientResponseException responseError) { diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java index 56791a43..21020f4a 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java @@ -43,6 +43,7 @@ import org.springframework.vault.support.VaultResponse; import org.springframework.vault.support.VaultResponseSupport; import org.springframework.vault.support.VaultToken; import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; /** @@ -344,7 +345,7 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa } @Override - public VaultResponse read(String path) { + public @Nullable VaultResponse read(String path) { Assert.hasText(path, "Path must not be empty"); @@ -352,8 +353,8 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa } @Override - @Nullable - public VaultResponseSupport read(String path, Class responseType) { + @SuppressWarnings("NullAway") + public @Nullable VaultResponseSupport read(String path, Class responseType) { ParameterizedTypeReference> ref = VaultResponses.getTypeReference(responseType); @@ -378,13 +379,13 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa @Override @SuppressWarnings("unchecked") - @Nullable - public List list(String path) { + public @Nullable List list(String path) { Assert.hasText(path, "Path must not be empty"); VaultListResponse read = doRead("%s?list=true".formatted(path.endsWith("/") ? path : (path + "/")), VaultListResponse.class); + if (read == null) { return Collections.emptyList(); } @@ -393,8 +394,8 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa } @Override - @Nullable - public VaultResponse write(String path, @Nullable Object body) { + @SuppressWarnings("NullAway") + public @Nullable VaultResponse write(String path, @Nullable Object body) { Assert.hasText(path, "Path must not be empty"); @@ -402,11 +403,12 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa } @Override + @SuppressWarnings("NullAway") public void delete(String path) { Assert.hasText(path, "Path must not be empty"); - doWithSession(restOperations -> { + doWithSession((RestOperationsCallback<@Nullable Void>) restOperations -> { try { restOperations.delete(path); @@ -425,7 +427,7 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa } @Override - public T doWithVault(RestOperationsCallback clientCallback) { + public T doWithVault(RestOperationsCallback clientCallback) { Assert.notNull(clientCallback, "Client callback must not be null"); @@ -438,7 +440,7 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa } @Override - public T doWithSession(RestOperationsCallback sessionCallback) { + public T doWithSession(RestOperationsCallback sessionCallback) { Assert.notNull(sessionCallback, "Session callback must not be null"); @@ -450,10 +452,10 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa } } - @Nullable - private T doRead(String path, Class responseType) { + @SuppressWarnings("NullAway") + private T doRead(String path, Class responseType) { - return doWithSession(restOperations -> { + return doWithSession((RestOperations restOperations) -> { try { return restOperations.getForObject(path, responseType); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTokenTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTokenTemplate.java index 900792d8..c1a201f4 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTokenTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTokenTemplate.java @@ -101,12 +101,13 @@ public class VaultTokenTemplate implements VaultTokenOperations { writeToken("auth/token/revoke-orphan", vaultToken, VaultTokenResponse.class); } + @SuppressWarnings("NullAway") private > T writeAndReturn(String path, @Nullable Object body, Class responseType) { Assert.hasText(path, "Path must not be empty"); - T response = this.vaultOperations.doWithSession(restOperations -> { + return this.vaultOperations.doWithSession(restOperations -> { try { ResponseEntity exchange = restOperations.exchange(path, HttpMethod.POST, body == null ? HttpEntity.EMPTY : new HttpEntity<>(body), responseType); @@ -117,17 +118,14 @@ public class VaultTokenTemplate implements VaultTokenOperations { throw VaultResponses.buildException(e, path); } }); - - Assert.state(response != null, "Response must not be null"); - - return response; } + @SuppressWarnings("NullAway") private void writeToken(String path, VaultToken token, Class responseType) { Assert.hasText(path, "Path must not be empty"); - this.vaultOperations.doWithSession(restOperations -> { + this.vaultOperations.doWithSession((RestOperationsCallback<@Nullable Void>) restOperations -> { try { restOperations.exchange(path, HttpMethod.POST, diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransformTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransformTemplate.java index 317a4bdf..4858ac1d 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransformTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransformTemplate.java @@ -22,6 +22,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.vault.VaultException; @@ -62,6 +64,7 @@ public class VaultTransformTemplate implements VaultTransformOperations { } @Override + @SuppressWarnings("NullAway") public String encode(String roleName, String plaintext) { Assert.hasText(roleName, "Role name must not be empty"); @@ -71,7 +74,7 @@ public class VaultTransformTemplate implements VaultTransformOperations { request.put("value", plaintext); - return (String) this.vaultOperations.write("%s/encode/%s".formatted(this.path, roleName), request) + return (String) this.vaultOperations.invoke("%s/encode/%s".formatted(this.path, roleName), request) .getRequiredData() .get("encoded_value"); } @@ -88,7 +91,7 @@ public class VaultTransformTemplate implements VaultTransformOperations { applyTransformOptions(plaintext.getContext(), request); - Map data = this.vaultOperations.write("%s/encode/%s".formatted(this.path, roleName), request) + Map data = this.vaultOperations.invoke("%s/encode/%s".formatted(this.path, roleName), request) .getRequiredData(); return toCiphertext(data, plaintext.getContext()); @@ -113,10 +116,10 @@ public class VaultTransformTemplate implements VaultTransformOperations { batch.add(vaultRequest); } - VaultResponse vaultResponse = this.vaultOperations.write("%s/encode/%s".formatted(this.path, roleName), + VaultResponse vaultResponse = this.vaultOperations.invoke("%s/encode/%s".formatted(this.path, roleName), Collections.singletonMap("batch_input", batch)); - return toEncodedResults(vaultResponse, batchRequest); + return toEncodedResults(requireResponse(vaultResponse), batchRequest); } @Override @@ -131,6 +134,7 @@ public class VaultTransformTemplate implements VaultTransformOperations { } @Override + @SuppressWarnings("NullAway") public String decode(String roleName, String ciphertext, VaultTransformContext transformContext) { Assert.hasText(roleName, "Role name must not be empty"); @@ -143,7 +147,7 @@ public class VaultTransformTemplate implements VaultTransformOperations { applyTransformOptions(transformContext, request); - return (String) this.vaultOperations.write("%s/decode/%s".formatted(this.path, roleName), request) + return (String) this.vaultOperations.invoke("%s/decode/%s".formatted(this.path, roleName), request) .getRequiredData() .get("decoded_value"); } @@ -166,10 +170,10 @@ public class VaultTransformTemplate implements VaultTransformOperations { batch.add(vaultRequest); } - VaultResponse vaultResponse = this.vaultOperations.write("%s/decode/%s".formatted(this.path, roleName), + VaultResponse vaultResponse = this.vaultOperations.invoke("%s/decode/%s".formatted(this.path, roleName), Collections.singletonMap("batch_input", batch)); - return toDecryptionResults(vaultResponse, batchRequest); + return toDecryptionResults(requireResponse(vaultResponse), batchRequest); } private static void applyTransformOptions(VaultTransformContext context, Map request) { @@ -253,6 +257,7 @@ public class VaultTransformTemplate implements VaultTransformOperations { return new VaultTransformDecodeResult(TransformPlaintext.empty().with(ciphertext.getContext())); } + @SuppressWarnings("NullAway") private static TransformCiphertext toCiphertext(Map data, VaultTransformContext context) { String ciphertext = (String) data.get("encoded_value"); @@ -279,9 +284,16 @@ public class VaultTransformTemplate implements VaultTransformOperations { return context; } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "NullAway", "unchecked" }) private static List> getBatchData(VaultResponse vaultResponse) { return (List>) vaultResponse.getRequiredData().get("batch_results"); } + private static T requireResponse(@Nullable T response) { + + Assert.state(response != null, "Response must not be null"); + + return response; + } + } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java index 754b1818..69602d6a 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTransitTemplate.java @@ -70,7 +70,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Assert.hasText(keyName, "Key name must not be empty"); - this.vaultOperations.write("%s/keys/%s".formatted(this.path, keyName), null); + writeForData("%s/keys/%s".formatted(this.path, keyName), null); } @Override @@ -79,15 +79,17 @@ public class VaultTransitTemplate implements VaultTransitOperations { Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(createKeyRequest, "VaultTransitKeyCreationRequest must not be empty"); - this.vaultOperations.write("%s/keys/%s".formatted(this.path, keyName), createKeyRequest); + writeForData("%s/keys/%s".formatted(this.path, keyName), createKeyRequest); } @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public List getKeys() { VaultResponse response = this.vaultOperations.read("%s/keys?list=true".formatted(this.path)); - return response == null ? Collections.emptyList() : (List) response.getRequiredData().get("keys"); + return response == null ? Collections.emptyList() + : (List) response.getRequiredData().getOrDefault("keys", Collections.emptyList()); } @Override @@ -96,7 +98,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Assert.hasText(keyName, "Key name must not be empty"); Assert.notNull(keyConfiguration, "VaultKeyConfiguration must not be empty"); - this.vaultOperations.write("%s/keys/%s/config".formatted(this.path, keyName), keyConfiguration); + writeForData("%s/keys/%s/config".formatted(this.path, keyName), keyConfiguration); } @Override @@ -141,10 +143,11 @@ public class VaultTransitTemplate implements VaultTransitOperations { Assert.hasText(keyName, "Key name must not be empty"); - this.vaultOperations.write("%s/keys/%s/rotate".formatted(this.path, keyName), null); + writeForData("%s/keys/%s/rotate".formatted(this.path, keyName), null); } @Override + @SuppressWarnings("NullAway") public String encrypt(String keyName, String plaintext) { Assert.hasText(keyName, "Key name must not be empty"); @@ -154,9 +157,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { request.put("plaintext", Base64.getEncoder().encodeToString(plaintext.getBytes())); - return (String) this.vaultOperations.write("%s/encrypt/%s".formatted(this.path, keyName), request) - .getRequiredData() - .get("ciphertext"); + return (String) writeForData("%s/encrypt/%s".formatted(this.path, keyName), request).get("ciphertext"); } @Override @@ -171,6 +172,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { } @Override + @SuppressWarnings("NullAway") public String encrypt(String keyName, byte[] plaintext, VaultTransitContext transitContext) { Assert.hasText(keyName, "Key name must not be empty"); @@ -183,9 +185,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { applyTransitOptions(transitContext, request); - return (String) this.vaultOperations.write("%s/encrypt/%s".formatted(this.path, keyName), request) - .getRequiredData() - .get("ciphertext"); + return (String) writeForData("%s/encrypt/%s".formatted(this.path, keyName), request).get("ciphertext"); } @Override @@ -201,15 +201,12 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map vaultRequest = new LinkedHashMap<>(2); vaultRequest.put("plaintext", Base64.getEncoder().encodeToString(request.getPlaintext())); - - if (request.getContext() != null) { - applyTransitOptions(request.getContext(), vaultRequest); - } + applyTransitOptions(request.getContext(), vaultRequest); batch.add(vaultRequest); } - VaultResponse vaultResponse = this.vaultOperations.write("%s/encrypt/%s".formatted(this.path, keyName), + VaultResponse vaultResponse = writeForResponse("%s/encrypt/%s".formatted(this.path, keyName), Collections.singletonMap("batch_input", batch)); return toBatchResults(vaultResponse, batchRequest, Plaintext::getContext); @@ -225,8 +222,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { request.put("ciphertext", ciphertext); - String plaintext = (String) this.vaultOperations.write("%s/decrypt/%s".formatted(this.path, keyName), request) - .getRequiredData() + String plaintext = (String) writeForData("%s/decrypt/%s".formatted(this.path, keyName), request) .get("plaintext"); return new String(Base64.getDecoder().decode(plaintext)); @@ -256,8 +252,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { applyTransitOptions(transitContext, request); - String plaintext = (String) this.vaultOperations.write("%s/decrypt/%s".formatted(this.path, keyName), request) - .getRequiredData() + String plaintext = (String) writeForData("%s/decrypt/%s".formatted(this.path, keyName), request) .get("plaintext"); return Base64.getDecoder().decode(plaintext); @@ -276,21 +271,19 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map vaultRequest = new LinkedHashMap<>(2); vaultRequest.put("ciphertext", request.getCiphertext()); - - if (request.getContext() != null) { - applyTransitOptions(request.getContext(), vaultRequest); - } + applyTransitOptions(request.getContext(), vaultRequest); batch.add(vaultRequest); } - VaultResponse vaultResponse = this.vaultOperations.write("%s/decrypt/%s".formatted(this.path, keyName), + VaultResponse vaultResponse = writeForResponse("%s/decrypt/%s".formatted(this.path, keyName), Collections.singletonMap("batch_input", batch)); return toDecryptionResults(vaultResponse, batchRequest); } @Override + @SuppressWarnings("NullAway") public String rewrap(String keyName, String ciphertext) { Assert.hasText(keyName, "Key name must not be empty"); @@ -299,12 +292,11 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map request = new LinkedHashMap<>(); request.put("ciphertext", ciphertext); - return (String) this.vaultOperations.write("%s/rewrap/%s".formatted(this.path, keyName), request) - .getRequiredData() - .get("ciphertext"); + return (String) writeForData("%s/rewrap/%s".formatted(this.path, keyName), request).get("ciphertext"); } @Override + @SuppressWarnings("NullAway") public String rewrap(String keyName, String ciphertext, VaultTransitContext transitContext) { Assert.hasText(keyName, "Key name must not be empty"); @@ -313,9 +305,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map request = createRewrapRequest(toCiphertext(ciphertext, transitContext)); - return (String) this.vaultOperations.write("%s/rewrap/%s".formatted(this.path, keyName), request) - .getRequiredData() - .get("ciphertext"); + return (String) writeForData("%s/rewrap/%s".formatted(this.path, keyName), request).get("ciphertext"); } @Override @@ -332,7 +322,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { batch.add(vaultRequest); } - VaultResponse vaultResponse = this.vaultOperations.write("%s/rewrap/%s".formatted(this.path, keyName), + VaultResponse vaultResponse = writeForResponse("%s/rewrap/%s".formatted(this.path, keyName), Collections.singletonMap("batch_input", batch)); return toBatchResults(vaultResponse, batchRequest, Ciphertext::getContext); @@ -350,6 +340,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { } @Override + @SuppressWarnings("NullAway") public Hmac getHmac(String keyName, VaultHmacRequest hmacRequest) { Assert.hasText(keyName, "Key name must not be empty"); @@ -357,9 +348,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map request = toRequestBody(hmacRequest); - String hmac = (String) this.vaultOperations.write("%s/hmac/%s".formatted(this.path, keyName), request) - .getRequiredData() - .get("hmac"); + String hmac = (String) writeForData("%s/hmac/%s".formatted(this.path, keyName), request).get("hmac"); return Hmac.of(hmac); } @@ -390,6 +379,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { } @Override + @SuppressWarnings("NullAway") public Signature sign(String keyName, VaultSignRequest signRequest) { Assert.hasText(keyName, "Key name must not be empty"); @@ -397,9 +387,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map request = toRequestBody(signRequest); - String signature = (String) this.vaultOperations.write("%s/sign/%s".formatted(this.path, keyName), request) - .getRequiredData() - .get("signature"); + String signature = (String) writeForData("%s/sign/%s".formatted(this.path, keyName), request).get("signature"); return Signature.of(signature); } @@ -438,8 +426,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { Map request = toRequestBody(verificationRequest); - Map response = this.vaultOperations.write("%s/verify/%s".formatted(this.path, keyName), request) - .getRequiredData(); + Map response = writeForData("%s/verify/%s".formatted(this.path, keyName), request); if (response.containsKey("valid") && Boolean.valueOf("" + response.get("valid"))) { return SignatureValidation.valid(); @@ -448,6 +435,21 @@ public class VaultTransitTemplate implements VaultTransitOperations { return SignatureValidation.invalid(); } + private Map writeForData(String path, @Nullable Object request) { + return writeForResponse(path, request).getRequiredData(); + } + + private VaultResponse writeForResponse(String path, @Nullable Object request) { + + VaultResponse response = this.vaultOperations.write(path, request); + + if (response == null) { + throw new IllegalStateException("Write to '%s' did not return a response".formatted(path)); + } + + return response; + } + static Map toRequestBody(VaultSignatureVerificationRequest verificationRequest) { Map request = new LinkedHashMap<>(5); @@ -483,6 +485,7 @@ public class VaultTransitTemplate implements VaultTransitOperations { } } + @SuppressWarnings("NullAway") static List toBatchResults(VaultResponse vaultResponse, List batchRequests, Function contextExtractor) { @@ -565,11 +568,12 @@ public class VaultTransitTemplate implements VaultTransitOperations { return context != null ? Ciphertext.of(ciphertext).with(context) : Ciphertext.of(ciphertext); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "NullAway", "unchecked" }) static List> getBatchData(VaultResponse vaultResponse) { return (List>) vaultResponse.getRequiredData().get("batch_results"); } + @SuppressWarnings("NullAway") static class VaultTransitKeyImpl implements VaultTransitKey { @Nullable @@ -625,7 +629,6 @@ public class VaultTransitTemplate implements VaultTransitOperations { } @Override - @Nullable public String getName() { return this.name; } @@ -827,10 +830,10 @@ public class VaultTransitTemplate implements VaultTransitOperations { private Map keys = Collections.emptyMap(); - @Nullable - private String name; + private final String name; - public RawTransitKeyImpl() { + public RawTransitKeyImpl(@JsonProperty("name") String name) { + this.name = name; } @Override @@ -839,7 +842,6 @@ public class VaultTransitTemplate implements VaultTransitOperations { } @Override - @Nullable public String getName() { return this.name; } @@ -848,10 +850,6 @@ public class VaultTransitTemplate implements VaultTransitOperations { this.keys = keys; } - public void setName(@Nullable String name) { - this.name = name; - } - @Override public boolean equals(Object o) { if (this == o) diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueTemplate.java index ed29b677..1ae23e2f 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueTemplate.java @@ -65,7 +65,7 @@ public class VaultVersionedKeyValueTemplate extends VaultKeyValue2Accessor imple @Nullable @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) public Versioned> get(String path, Version version) { Assert.hasText(path, "Path must not be empty"); @@ -86,29 +86,31 @@ public class VaultVersionedKeyValueTemplate extends VaultKeyValue2Accessor imple } @Nullable + @SuppressWarnings("NullAway") private Versioned doRead(String path, Version version, Class responseType) { String secretPath = version.isVersioned() ? "%s?version=%d".formatted(createDataPath(path), version.getVersion()) : createDataPath(path); - VersionedResponse response = this.vaultOperations.doWithSession(restOperations -> { + VersionedResponse response = this.vaultOperations + .doWithSession((RestOperationsCallback<@Nullable VersionedResponse>) restOperations -> { - try { - return restOperations.exchange(secretPath, HttpMethod.GET, null, VersionedResponse.class).getBody(); - } - catch (HttpStatusCodeException e) { + try { + return restOperations.exchange(secretPath, HttpMethod.GET, null, VersionedResponse.class).getBody(); + } + catch (HttpStatusCodeException e) { - if (HttpStatusUtil.isNotFound(e.getStatusCode())) { - if (e.getResponseBodyAsString().contains("deletion_time")) { - return VaultResponses.unwrap(e.getResponseBodyAsString(), VersionedResponse.class); + if (HttpStatusUtil.isNotFound(e.getStatusCode())) { + if (e.getResponseBodyAsString().contains("deletion_time")) { + return VaultResponses.unwrap(e.getResponseBodyAsString(), VersionedResponse.class); + } + + return null; } - return null; + throw VaultResponses.buildException(e, path); } - - throw VaultResponses.buildException(e, path); - } - }); + }); if (response == null) { return null; @@ -132,7 +134,7 @@ public class VaultVersionedKeyValueTemplate extends VaultKeyValue2Accessor imple if (body instanceof Versioned versioned) { - data.put("data", versioned.getData()); + data.put("data", versioned.getRequiredData()); data.put("options", requestOptions); requestOptions.put("cas", versioned.getVersion().getVersion()); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultWrappingTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultWrappingTemplate.java index ac9e67ea..a46b73b5 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultWrappingTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultWrappingTemplate.java @@ -83,7 +83,7 @@ public class VaultWrappingTemplate implements VaultWrappingOperations { return null; } - return getWrappedMetadata(response.getData(), token); + return getWrappedMetadata(response.getRequiredData(), token); } @Nullable @@ -107,11 +107,11 @@ public class VaultWrappingTemplate implements VaultWrappingOperations { }); } - @Nullable - private > T doUnwrap(VaultToken token, - BiFunction, T> requestFunction) { + @SuppressWarnings("NullAway") + private > @Nullable T doUnwrap(VaultToken token, + BiFunction, @Nullable T> requestFunction) { - return this.vaultOperations.doWithVault(restOperations -> { + return this.vaultOperations.doWithVault((RestOperationsCallback<@Nullable T>) restOperations -> { try { return requestFunction.apply(restOperations, new HttpEntity<>(VaultHttpHeaders.from(token))); @@ -133,11 +133,12 @@ public class VaultWrappingTemplate implements VaultWrappingOperations { } @Override + @SuppressWarnings("NullAway") public WrappedMetadata rewrap(VaultToken token) { Assert.notNull(token, "token VaultToken not be null"); - VaultResponse response = this.vaultOperations.write("sys/wrapping/rewrap", + VaultResponse response = this.vaultOperations.invoke("sys/wrapping/rewrap", Collections.singletonMap("token", token.getToken())); Map wrapInfo = response.getWrapInfo(); @@ -146,6 +147,7 @@ public class VaultWrappingTemplate implements VaultWrappingOperations { } @Override + @SuppressWarnings("NullAway") public WrappedMetadata wrap(Object body, Duration duration) { Assert.notNull(body, "Body must not be null"); @@ -175,15 +177,17 @@ public class VaultWrappingTemplate implements VaultWrappingOperations { return new WrappedMetadata(token, ttl, Instant.from(creation_time), path); } - @Nullable private static TemporalAccessor getDate(Map responseMetadata, String key) { String date = (String) ((Map) responseMetadata).getOrDefault(key, ""); - return StringUtils.hasText(date) ? DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(date) : null; + if (StringUtils.hasText(date)) { + return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(date); + } + + throw new IllegalArgumentException("Cannot obtain date"); } - @Nullable private static Duration getTtl(Map wrapInfo) { Object creationTtl = wrapInfo.get("ttl"); @@ -196,13 +200,12 @@ public class VaultWrappingTemplate implements VaultWrappingOperations { creationTtl = Integer.parseInt((String) creationTtl); } - Duration ttl = null; - if (creationTtl instanceof Integer) { - ttl = Duration.ofSeconds((Integer) creationTtl); + return Duration.ofSeconds((Integer) creationTtl); } - return ttl; + + throw new IllegalArgumentException("Cannot obtain TTL"); } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/env/LeaseAwareVaultPropertySource.java b/spring-vault-core/src/main/java/org/springframework/vault/core/env/LeaseAwareVaultPropertySource.java index d10fb87a..7a2e5dff 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/env/LeaseAwareVaultPropertySource.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/env/LeaseAwareVaultPropertySource.java @@ -199,7 +199,7 @@ public class LeaseAwareVaultPropertySource extends EnumerablePropertySource strings = this.properties.keySet(); - return strings.toArray(new String[strings.size()]); + return strings.toArray(new String[0]); } // ------------------------------------------------------------------------- diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/env/VaultPropertySource.java b/spring-vault-core/src/main/java/org/springframework/vault/core/env/VaultPropertySource.java index 2a3d90dd..b604cf89 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/env/VaultPropertySource.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/env/VaultPropertySource.java @@ -185,7 +185,7 @@ public class VaultPropertySource extends EnumerablePropertySource strings = this.properties.keySet(); - return strings.toArray(new String[strings.size()]); + return strings.toArray(new String[0]); } // ------------------------------------------------------------------------- @@ -206,8 +206,7 @@ public class VaultPropertySource extends EnumerablePropertySource doGetProperties(String path) throws VaultException { + protected @Nullable Map doGetProperties(String path) throws VaultException { VaultResponse vaultResponse; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java index 11b307ad..51af4c10 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java @@ -46,7 +46,7 @@ public enum LeaseEndpoints { public void revoke(Lease lease, RestOperations operations) { operations.exchange("sys/revoke", HttpMethod.PUT, LeaseEndpoints.getLeaseRevocationBody(lease), Map.class, - lease.getLeaseId()); + lease.getRequiredLeaseId()); } @Override @@ -72,7 +72,7 @@ public enum LeaseEndpoints { public void revoke(Lease lease, RestOperations operations) { operations.exchange("sys/leases/revoke", HttpMethod.PUT, LeaseEndpoints.getLeaseRevocationBody(lease), - Map.class, lease.getLeaseId()); + Map.class, lease.getRequiredLeaseId()); } @Override @@ -97,7 +97,7 @@ public enum LeaseEndpoints { @Override public void revoke(Lease lease, RestOperations operations) { - String endpoint = "sys/leases/revoke-prefix/" + lease.getLeaseId(); + String endpoint = "sys/leases/revoke-prefix/" + lease.getRequiredLeaseId(); operations.put(endpoint, null); } @@ -129,6 +129,7 @@ public enum LeaseEndpoints { */ abstract Lease renew(Lease lease, RestOperations operations); + @SuppressWarnings("NullAway") private static Lease toLease(Map body) { String leaseId = (String) body.get("lease_id"); @@ -141,7 +142,7 @@ public enum LeaseEndpoints { private static HttpEntity getLeaseRenewalBody(Lease lease) { Map leaseRenewalData = new HashMap<>(); - leaseRenewalData.put("lease_id", lease.getLeaseId()); + leaseRenewalData.put("lease_id", lease.getRequiredLeaseId()); leaseRenewalData.put("increment", Long.toString(lease.getLeaseDuration().getSeconds())); return new HttpEntity<>(leaseRenewalData); @@ -150,7 +151,7 @@ public enum LeaseEndpoints { private static HttpEntity getLeaseRevocationBody(Lease lease) { Map leaseRenewalData = new HashMap<>(); - leaseRenewalData.put("lease_id", lease.getLeaseId()); + leaseRenewalData.put("lease_id", lease.getRequiredLeaseId()); return new HttpEntity<>(leaseRenewalData); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/SecretLeaseContainer.java b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/SecretLeaseContainer.java index 970883ac..b57f8a38 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/SecretLeaseContainer.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/SecretLeaseContainer.java @@ -61,6 +61,7 @@ import org.springframework.vault.authentication.event.AuthenticationListener; import org.springframework.vault.authentication.event.LoginTokenExpiredEvent; import org.springframework.vault.authentication.event.LoginTokenRenewalFailedEvent; import org.springframework.vault.client.VaultResponses; +import org.springframework.vault.core.RestOperationsCallback; import org.springframework.vault.core.VaultOperations; import org.springframework.vault.core.lease.domain.Lease; import org.springframework.vault.core.lease.domain.RequestedSecret; @@ -560,6 +561,8 @@ public class SecretLeaseContainer extends SecretLeaseEventPublisher void restartSecrets() { + Assert.state(this.taskScheduler != null, "TaskScheduler is not set"); + int status = this.status; if (status == STATUS_STARTED) { @@ -865,14 +868,14 @@ public class SecretLeaseContainer extends SecretLeaseEventPublisher * @param requestedSecret must not be {@literal null}. * @param lease must not be {@literal null}. */ - @SuppressWarnings("unchecked") + @SuppressWarnings("NullAway") protected void doRevokeLease(RequestedSecret requestedSecret, Lease lease) { try { onBeforeLeaseRevocation(requestedSecret, lease); - this.operations.doWithSession(restOperations -> { + this.operations.doWithSession((RestOperationsCallback<@Nullable Void>) restOperations -> { this.leaseEndpoints.revoke(lease, restOperations); return null; }); @@ -1112,6 +1115,8 @@ public class SecretLeaseContainer extends SecretLeaseEventPublisher */ private void restartSecrets() { + Assert.state(taskScheduler != null, "TaskScheduler is not set"); + if (!isRunning()) { logger.debug("Ignore token event as the container is not running"); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/domain/Lease.java b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/domain/Lease.java index b01a67ce..3c536385 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/domain/Lease.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/domain/Lease.java @@ -94,13 +94,27 @@ public class Lease { } /** - * @return the lease Id + * @return the lease Id. */ - @Nullable - public String getLeaseId() { + public @Nullable String getLeaseId() { return this.leaseId; } + /** + * @return the required lease Id. + * @since 4.0 + */ + public String getRequiredLeaseId() { + + String leaseId = getLeaseId(); + + if (leaseId == null) { + throw new IllegalStateException("No leaseId available."); + } + + return leaseId; + } + /** * @return the lease duration in seconds. */ diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/event/SecretLeaseRotatedEvent.java b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/event/SecretLeaseRotatedEvent.java index 9608084e..c77300d4 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/event/SecretLeaseRotatedEvent.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/event/SecretLeaseRotatedEvent.java @@ -48,6 +48,7 @@ public class SecretLeaseRotatedEvent extends SecretLeaseCreatedEvent { return this.previousLease; } + @SuppressWarnings("NullAway") public Lease getCurrentLease() { return getLease(); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/util/KeyValueDelegate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/util/KeyValueDelegate.java index 735f6978..72852221 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/util/KeyValueDelegate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/util/KeyValueDelegate.java @@ -79,7 +79,7 @@ public class KeyValueDelegate { MountInfo mountInfo = this.mountInfo.get(path); - if (!mountInfo.isKeyValue(KeyValueBackend.versioned())) { + if (mountInfo == null || !mountInfo.isKeyValue(KeyValueBackend.versioned())) { return this.operations.read(path); } @@ -111,7 +111,7 @@ public class KeyValueDelegate { response.setData(nested); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "NullAway" }) private MountInfo doGetMountInfo(String path) { VaultResponse response = this.operations.read("sys/internal/ui/mounts/%s".formatted(path)); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/DefaultVaultTypeMapper.java b/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/DefaultVaultTypeMapper.java index 7c361c13..c77c667c 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/DefaultVaultTypeMapper.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/DefaultVaultTypeMapper.java @@ -82,7 +82,7 @@ public class DefaultVaultTypeMapper extends DefaultTypeMapper> accessor, - MappingContext, ?> mappingContext, + @Nullable MappingContext, ?> mappingContext, List mappers) { super(accessor, mappingContext, mappers); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/MappingVaultConverter.java b/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/MappingVaultConverter.java index 4f5d44ad..6f2f1de1 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/MappingVaultConverter.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/MappingVaultConverter.java @@ -38,6 +38,7 @@ import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; import org.springframework.data.mapping.model.PropertyValueProvider; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -105,7 +106,11 @@ public class MappingVaultConverter extends AbstractVaultConverter { Class rawType = typeToUse.getType(); if (this.conversions.hasCustomReadTarget(source.getClass(), rawType)) { - return this.conversionService.convert(source, rawType); + + S result = this.conversionService.convert(source, rawType); + + Assert.state(result != null, "Conversion result must not be null"); + return result; } if (SecretDocument.class.isAssignableFrom(rawType)) { @@ -124,22 +129,26 @@ public class MappingVaultConverter extends AbstractVaultConverter { return (S) source; } + if (secretDocument == null) { + throw new MappingException("Unsupported source type: " + source.getClass().getName()); + } + return read((VaultPersistentEntity) this.mappingContext.getRequiredPersistentEntity(typeToUse), secretDocument); } - @Nullable @SuppressWarnings("unchecked") - private SecretDocument getSecretDocument(Object source) { + private @Nullable SecretDocument getSecretDocument(Object source) { - SecretDocument secretDocument = null; if (source instanceof Map) { - secretDocument = new SecretDocument((Map) source); + return new SecretDocument((Map) source); } - else if (source instanceof SecretDocument) { - secretDocument = (SecretDocument) source; + + if (source instanceof SecretDocument) { + return (SecretDocument) source; } - return secretDocument; + + return null; } private ParameterValueProvider getParameterProvider(VaultPersistentEntity entity, @@ -152,9 +161,8 @@ public class MappingVaultConverter extends AbstractVaultConverter { return new ParameterValueProvider() { - @Nullable @Override - public T getParameterValue(Parameter parameter) { + public @Nullable T getParameterValue(Parameter parameter) { Object value = parameterProvider.getParameterValue(parameter); return value != null ? readValue(value, parameter.getType()) : null; } @@ -246,7 +254,7 @@ public class MappingVaultConverter extends AbstractVaultConverter { * @return the converted {@link Collection} or array, will never be {@literal null}. */ @Nullable - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({ "rawtypes" }) private Object readCollectionOrArray(TypeInformation targetType, List sourceValue) { Assert.notNull(targetType, "Target type must not be null"); @@ -258,7 +266,7 @@ public class MappingVaultConverter extends AbstractVaultConverter { Class rawComponentType = componentType.getType(); collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class; - Collection items = targetType.getType().isArray() ? new ArrayList<>(sourceValue.size()) + Collection<@Nullable Object> items = targetType.getType().isArray() ? new ArrayList<>(sourceValue.size()) : CollectionFactory.createCollection(collectionType, rawComponentType, sourceValue.size()); if (sourceValue.isEmpty()) { @@ -289,7 +297,7 @@ public class MappingVaultConverter extends AbstractVaultConverter { * @param sourceMap must not be {@literal null} * @return the converted {@link Map}. */ - protected Map readMap(TypeInformation type, Map sourceMap) { + protected Map<@Nullable Object, @Nullable Object> readMap(TypeInformation type, Map sourceMap) { Assert.notNull(sourceMap, "Source map must not be null"); @@ -301,7 +309,8 @@ public class MappingVaultConverter extends AbstractVaultConverter { Class rawKeyType = keyType != null ? keyType.getType() : null; Class rawValueType = valueType != null ? valueType.getType() : null; - Map map = CollectionFactory.createMap(mapType, rawKeyType, sourceMap.keySet().size()); + Map<@Nullable Object, @Nullable Object> map = CollectionFactory.createMap(mapType, rawKeyType, + sourceMap.keySet().size()); for (Entry entry : sourceMap.entrySet()) { @@ -386,6 +395,8 @@ public class MappingVaultConverter extends AbstractVaultConverter { SecretDocument result = this.conversionService.convert(obj, SecretDocument.class); + Assert.state(result != null, "Custom conversion must not return null"); + if (result.getId() != null) { sink.setId(result.getId()); } @@ -511,7 +522,7 @@ public class MappingVaultConverter extends AbstractVaultConverter { * @param sink the {@link List} to write to. * @return the converted {@link List}. */ - private List writeCollectionInternal(Collection source, @Nullable TypeInformation type, + private List writeCollectionInternal(Collection<@Nullable ?> source, @Nullable TypeInformation type, List sink) { TypeInformation componentType = null; @@ -609,7 +620,7 @@ public class MappingVaultConverter extends AbstractVaultConverter { protected void addCustomTypeKeyIfNecessary(@Nullable TypeInformation type, Object value, SecretDocumentAccessor accessor) { - Class reference = type != null ? type.getActualType().getType() : Object.class; + Class reference = type != null ? type.getRequiredActualType().getType() : Object.class; Class valueType = ClassUtils.getUserClass(value.getClass()); boolean notTheSameClass = !valueType.equals(reference); @@ -626,8 +637,9 @@ public class MappingVaultConverter extends AbstractVaultConverter { * @param targetType * @return the converted value. Can be {@literal null}. */ - @Nullable - private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, Class targetType) { + + @Contract("null, _ -> null; !null, _ -> !null") + private @Nullable Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, Class targetType) { if (value == null) { return null; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocument.java b/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocument.java index dcca7047..c20927bf 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocument.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocument.java @@ -100,16 +100,14 @@ public class SecretDocument { * @param vaultResponse must not be {@literal null}. * @return the {@link SecretDocument}. */ - @SuppressWarnings("ConstantConditions") public static SecretDocument from(@Nullable String id, VaultResponse vaultResponse) { - return new SecretDocument(id, vaultResponse.getData()); + return new SecretDocument(id, vaultResponse.getRequiredData()); } /** * @return the identifier or {@literal null} if the identifier is not set. */ - @Nullable - public String getId() { + public @Nullable String getId() { return this.id; } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocumentAccessor.java b/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocumentAccessor.java index a025d245..ee12aee2 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocumentAccessor.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocumentAccessor.java @@ -35,7 +35,7 @@ import org.springframework.vault.repository.mapping.VaultPersistentProperty; * @author Mark Paluch * @since 2.0 */ -class SecretDocumentAccessor { +public class SecretDocumentAccessor { private final SecretDocument document; @@ -204,8 +204,7 @@ class SecretDocumentAccessor { * @return */ @SuppressWarnings("unchecked") - @Nullable - private static Map getAsMap(Object source) { + private @Nullable static Map getAsMap(@Nullable Object source) { if (source instanceof Map) { return (Map) source; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/security/VaultBytesKeyGenerator.java b/spring-vault-core/src/main/java/org/springframework/vault/security/VaultBytesKeyGenerator.java index f47dc26c..b9eb72f9 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/security/VaultBytesKeyGenerator.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/security/VaultBytesKeyGenerator.java @@ -75,6 +75,7 @@ public class VaultBytesKeyGenerator implements BytesKeyGenerator { } @Override + @SuppressWarnings("NullAway") public byte[] generateKey() { VaultResponse response = this.vaultOperations.write("%s/random/%d".formatted(this.transitPath, getKeyLength()), diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/AbstractResult.java b/spring-vault-core/src/main/java/org/springframework/vault/support/AbstractResult.java index ab21107e..82b6dcc5 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/AbstractResult.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/AbstractResult.java @@ -64,8 +64,7 @@ public abstract class AbstractResult { * Returns the cause of the failed operation if the operation completed with an error. * @return the cause of the failure or {@literal null} if succeeded. */ - @Nullable - public Exception getCause() { + public @Nullable Exception getCause() { return this.exception; } @@ -76,8 +75,9 @@ public abstract class AbstractResult { * @return the result value. * @throws VaultException if the operation completed with an error. */ - @Nullable - public V get() { + + @SuppressWarnings("NullAway") + public @Nullable V get() { if (isSuccessful()) { return get0(); @@ -89,7 +89,6 @@ public abstract class AbstractResult { /** * @return the actual result if this result completed successfully. */ - @Nullable - protected abstract V get0(); + protected abstract @Nullable V get0(); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java b/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java index 5025b8b6..bff266e3 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/CertificateBundle.java @@ -70,7 +70,7 @@ public class CertificateBundle extends Certificate { @JsonProperty("certificate") String certificate, @JsonProperty("issuing_ca") String issuingCaCertificate, @JsonProperty("ca_chain") List caChain, @JsonProperty("private_key") String privateKey, @Nullable @JsonProperty("private_key_type") String privateKeyType, - @JsonProperty("revocation_time") Long revocationTime) { + @Nullable @JsonProperty("revocation_time") Long revocationTime) { super(serialNumber, certificate, issuingCaCertificate, caChain, revocationTime); this.privateKey = privateKey; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/DurationParser.java b/spring-vault-core/src/main/java/org/springframework/vault/support/DurationParser.java index d3bffd7d..21cb671e 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/DurationParser.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/DurationParser.java @@ -24,6 +24,7 @@ import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; import org.springframework.util.ObjectUtils; /** @@ -45,8 +46,8 @@ public class DurationParser { * @return the duration object. Can be {@literal null} if {@code duration} is empty. * @throws IllegalArgumentException if unable to parse the requested duration. */ - @Nullable - public static Duration parseDuration(String duration) { + @Contract("null -> null") + public static @Nullable Duration parseDuration(@Nullable String duration) { if (ObjectUtils.isEmpty(duration)) { return null; @@ -69,13 +70,13 @@ public class DurationParser { result = switch (typ) { case "ns" -> result.plus(Duration.ofNanos(num)); - case "us" -> result.plus(Duration.ofNanos(num * 1000)); + case "us" -> result.plus(Duration.ofNanos(num * 1000L)); case "ms" -> result.plus(Duration.ofMillis(num)); case "s" -> result.plus(Duration.ofSeconds(num)); case "m" -> result.plus(Duration.ofMinutes(num)); case "h" -> result.plus(Duration.ofHours(num)); case "d" -> result.plus(Duration.ofDays(num)); - case "w" -> result.plus(Duration.ofDays(num * 7)); + case "w" -> result.plus(Duration.ofDays(num * 7L)); default -> result; }; } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/PemObject.java b/spring-vault-core/src/main/java/org/springframework/vault/support/PemObject.java index 580e25dc..1fcf2c19 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/PemObject.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/PemObject.java @@ -32,6 +32,7 @@ import java.util.regex.Pattern; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; import org.springframework.util.Assert; /** @@ -143,7 +144,6 @@ public class PemObject { StringBuilder keyBuilder = null; while (true) { String line = reader.readLine(); - ; if (line == null) { Assert.isTrue(title == null, "missing end tag " + title); return null; @@ -299,7 +299,8 @@ public class PemObject { return name; } - public static PemObjectType of(String identifier) { + @Contract("null -> fail") + public static PemObjectType of(@Nullable String identifier) { Assert.hasText(identifier, "Identifier must not be empty"); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/Policy.java b/spring-vault-core/src/main/java/org/springframework/vault/support/Policy.java index ef55b2ef..1721900a 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/Policy.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/Policy.java @@ -48,8 +48,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.util.Converter; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.vault.support.Policy.PolicyDeserializer; @@ -138,8 +138,7 @@ public class Policy { * @param path must not be {@literal null}. * @return the {@link Rule} or {@literal null}, if not found. */ - @Nullable - public Rule getRule(String path) { + public @Nullable Rule getRule(String path) { Assert.notNull(path, "Path must not be null"); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/SslConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/support/SslConfiguration.java index abf8d90b..6fee8d7a 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/SslConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/SslConfiguration.java @@ -161,7 +161,7 @@ public class SslConfiguration { * @return the created {@link SslConfiguration}. * @see java.security.KeyStore */ - public static SslConfiguration forTrustStore(Resource trustStore, char @Nullable[] trustStorePassword) { + public static SslConfiguration forTrustStore(Resource trustStore, char @Nullable [] trustStorePassword) { Assert.notNull(trustStore, "TrustStore must not be null"); Assert.isTrue(trustStore.exists(), () -> "TrustStore %s does not exist".formatted(trustStore)); @@ -192,7 +192,7 @@ public class SslConfiguration { * @return the created {@link SslConfiguration}. * @see java.security.KeyStore */ - public static SslConfiguration forKeyStore(Resource keyStore, char @Nullable[] keyStorePassword) { + public static SslConfiguration forKeyStore(Resource keyStore, char @Nullable [] keyStorePassword) { return forKeyStore(new KeyStoreConfiguration(keyStore, keyStorePassword, DEFAULT_KEYSTORE_TYPE), KeyConfiguration.unconfigured()); } @@ -237,7 +237,7 @@ public class SslConfiguration { * @since 2.2 * @see java.security.KeyStore */ - public static SslConfiguration forKeyStore(Resource keyStore, char @Nullable[] keyStorePassword, + public static SslConfiguration forKeyStore(Resource keyStore, char @Nullable [] keyStorePassword, KeyConfiguration keyConfiguration) { Assert.notNull(keyStore, "KeyStore must not be null"); @@ -260,8 +260,8 @@ public class SslConfiguration { * @return the created {@link SslConfiguration}. * @see java.security.KeyStore */ - public static SslConfiguration create(Resource keyStore, char @Nullable[] keyStorePassword, Resource trustStore, - char @Nullable[] trustStorePassword) { + public static SslConfiguration create(Resource keyStore, char @Nullable [] keyStorePassword, Resource trustStore, + char @Nullable [] trustStorePassword) { Assert.notNull(keyStore, "KeyStore must not be null"); Assert.isTrue(keyStore.exists(), () -> "KeyStore %s does not exist".formatted(keyStore)); @@ -441,12 +441,11 @@ public class SslConfiguration { } @Nullable - private static String stringOrNull(char @Nullable[] storePassword) { + private static String stringOrNull(char @Nullable [] storePassword) { return storePassword != null ? new String(storePassword) : null; } - - private static char @Nullable[] charsOrNull(@Nullable String trustStorePassword) { + private static char @Nullable [] charsOrNull(@Nullable String trustStorePassword) { return trustStorePassword != null ? trustStorePassword.toCharArray() : null; } @@ -465,11 +464,10 @@ public class SslConfiguration { */ private final Resource resource; - /** * Password used to access the key store/trust store. */ - private final char @Nullable[] storePassword; + private final char @Nullable [] storePassword; /** * Key store/trust store type. @@ -479,7 +477,7 @@ public class SslConfiguration { /** * Create a new {@link KeyStoreConfiguration}. */ - public KeyStoreConfiguration(Resource resource, char @Nullable[] storePassword, String storeType) { + public KeyStoreConfiguration(Resource resource, char @Nullable [] storePassword, String storeType) { Assert.notNull(resource, "Resource must not be null"); Assert.isTrue(resource instanceof AbsentResource || resource.exists(), @@ -517,7 +515,7 @@ public class SslConfiguration { * @return the {@link KeyStoreConfiguration} for {@code resource}. * @since 2.0 */ - public static KeyStoreConfiguration of(Resource resource, char @Nullable[] storePassword) { + public static KeyStoreConfiguration of(Resource resource, char @Nullable [] storePassword) { return of(resource, storePassword, DEFAULT_KEYSTORE_TYPE); } @@ -531,7 +529,8 @@ public class SslConfiguration { * @return the {@link KeyStoreConfiguration} for {@code resource}. * @since 2.3 */ - public static KeyStoreConfiguration of(Resource resource, char @Nullable[] storePassword, String keyStoreType) { + public static KeyStoreConfiguration of(Resource resource, char @Nullable [] storePassword, + String keyStoreType) { return new KeyStoreConfiguration(resource, storePassword, keyStoreType); } @@ -560,12 +559,11 @@ public class SslConfiguration { return this.resource; } - /** * @return the key store/trust store password. Empty {@code char} array if not * set. */ - public char @Nullable[] getStorePassword() { + public char @Nullable [] getStorePassword() { return this.storePassword; } @@ -601,11 +599,11 @@ public class SslConfiguration { private static final KeyConfiguration UNCONFIGURED = new KeyConfiguration(null, null); - private final char @Nullable[] keyPassword; + private final char @Nullable [] keyPassword; private final @Nullable String keyAlias; - private KeyConfiguration(char @Nullable[] keyPassword, @Nullable String keyAlias) { + private KeyConfiguration(char @Nullable [] keyPassword, @Nullable String keyAlias) { if (keyPassword == null) { this.keyPassword = null; @@ -634,15 +632,14 @@ public class SslConfiguration { * . * @return the {@link KeyConfiguration}. */ - public static KeyConfiguration of(char @Nullable[] keyPassword, @Nullable String keyAlias) { + public static KeyConfiguration of(char @Nullable [] keyPassword, @Nullable String keyAlias) { return new KeyConfiguration(keyPassword, keyAlias); } - /** * @return the key password to use. */ - public char @Nullable[] getKeyPassword() { + public char @Nullable [] getKeyPassword() { return this.keyPassword; } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultMetadataResponse.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultMetadataResponse.java index eea8ba41..66a6ee51 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultMetadataResponse.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultMetadataResponse.java @@ -24,6 +24,7 @@ import java.util.Map; import org.jspecify.annotations.Nullable; +import org.springframework.util.Assert; import org.springframework.vault.support.Versioned.Metadata; /** @@ -43,7 +44,7 @@ public class VaultMetadataResponse { private final Map customMetadata; - private final Duration deleteVersionAfter; + private final @Nullable Duration deleteVersionAfter; private final int maxVersions; @@ -54,8 +55,8 @@ public class VaultMetadataResponse { private final List versions; private VaultMetadataResponse(boolean casRequired, Instant createdTime, int currentVersion, - Map customMetadata, Duration deleteVersionAfter, int maxVersions, int oldestVersion, - Instant updatedTime, List versions) { + Map customMetadata, @Nullable Duration deleteVersionAfter, int maxVersions, + int oldestVersion, Instant updatedTime, List versions) { this.casRequired = casRequired; this.createdTime = createdTime; @@ -97,8 +98,7 @@ public class VaultMetadataResponse { * @return the duration after which a secret is to be deleted. {@link Period#ZERO} for * unlimited duration. Versions prior to Vault 1.2 may return {@code null}. */ - @Nullable - public Duration getDeleteVersionAfter() { + public @Nullable Duration getDeleteVersionAfter() { return this.deleteVersionAfter; } @@ -146,21 +146,21 @@ public class VaultMetadataResponse { private boolean casRequired; - private Map customMetadata; + private Map customMetadata = Collections.emptyMap(); - private Instant createdTime; + private @Nullable Instant createdTime; private int currentVersion; - private Duration deleteVersionAfter; + private @Nullable Duration deleteVersionAfter; private int maxVersions; private int oldestVersion; - private Instant updatedTime; + private @Nullable Instant updatedTime; - private List versions; + private List versions = Collections.emptyList(); public VaultMetadataResponseBuilder casRequired(boolean casRequired) { this.casRequired = casRequired; @@ -182,7 +182,7 @@ public class VaultMetadataResponse { return this; } - public VaultMetadataResponseBuilder deleteVersionAfter(Duration deleteVersionAfter) { + public VaultMetadataResponseBuilder deleteVersionAfter(@Nullable Duration deleteVersionAfter) { this.deleteVersionAfter = deleteVersionAfter; return this; } @@ -208,6 +208,10 @@ public class VaultMetadataResponse { } public VaultMetadataResponse build() { + + Assert.notNull(this.createdTime, "Created time must not be null"); + Assert.notNull(this.updatedTime, "Updated time must not be null"); + return new VaultMetadataResponse(this.casRequired, this.createdTime, this.currentVersion, this.customMetadata, this.deleteVersionAfter, this.maxVersions, this.oldestVersion, this.updatedTime, this.versions); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultMount.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultMount.java index ce56f25e..643675e0 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultMount.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultMount.java @@ -21,6 +21,7 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonProperty; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; import org.springframework.util.Assert; /** @@ -119,11 +120,9 @@ public class VaultMount { */ public static class VaultMountBuilder { - @Nullable - private String type; + private @Nullable String type; - @Nullable - private String description; + private @Nullable String description; private Map config = Collections.emptyMap(); @@ -137,7 +136,8 @@ public class VaultMount { * @param type the backend type, must not be empty or {@literal null}. * @return {@literal this} {@link VaultMountBuilder}. */ - public VaultMountBuilder type(String type) { + @Contract("null -> fail") + public VaultMountBuilder type(@Nullable String type) { Assert.hasText(type, "Type must not be empty or null"); @@ -146,11 +146,11 @@ public class VaultMount { } /** - * Configure a human readable description of this mount. - * @param description a human readable description of this mount. + * Configure a human-readable description of this mount. + * @param description a human-readable description of this mount. * @return {@literal this} {@link VaultMountBuilder}. */ - public VaultMountBuilder description(String description) { + public VaultMountBuilder description(@Nullable String description) { this.description = description; return this; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultResponseSupport.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultResponseSupport.java index 58fe9948..7f77dea8 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultResponseSupport.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultResponseSupport.java @@ -16,6 +16,7 @@ package org.springframework.vault.support; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -33,18 +34,15 @@ import org.jspecify.annotations.Nullable; @JsonIgnoreProperties(ignoreUnknown = true) public class VaultResponseSupport { - @Nullable - private Map auth; + private Map auth = Collections.emptyMap(); @Nullable private T data; - @Nullable - private Map metadata; + private Map metadata = Collections.emptyMap(); @JsonProperty("wrap_info") - @Nullable - private Map wrapInfo; + private Map wrapInfo = Collections.emptyMap(); @JsonProperty("lease_duration") private long leaseDuration; @@ -59,8 +57,7 @@ public class VaultResponseSupport { private boolean renewable; - @Nullable - private List warnings; + private List warnings = Collections.emptyList(); /** * Apply metadata such as auth or warnings without copying data. @@ -82,28 +79,14 @@ public class VaultResponseSupport { /** * @return authentication payload. */ - @Nullable public Map getAuth() { return this.auth; } - /** - * @return the authentication payload. - * @throws IllegalStateException if {@code auth} is null. - */ - public Map getRequiredAuth() { - - if (this.auth != null) { - return this.auth; - } - - throw new IllegalStateException("Auth field is empty"); - } - /** * @param auth the authentication payload. */ - public void setAuth(@Nullable Map auth) { + public void setAuth(Map auth) { this.auth = auth; } @@ -138,7 +121,6 @@ public class VaultResponseSupport { /** * @return request metadata. */ - @Nullable public Map getMetadata() { return this.metadata; } @@ -146,7 +128,7 @@ public class VaultResponseSupport { /** * @param metadata request metadata. */ - public void setMetadata(@Nullable Map metadata) { + public void setMetadata(Map metadata) { this.metadata = metadata; } @@ -196,7 +178,6 @@ public class VaultResponseSupport { /** * @return response wrapping details. */ - @Nullable public Map getWrapInfo() { return this.wrapInfo; } @@ -204,7 +185,7 @@ public class VaultResponseSupport { /** * @param wrapInfo response wrapping details. */ - public void setWrapInfo(@Nullable Map wrapInfo) { + public void setWrapInfo(Map wrapInfo) { this.wrapInfo = wrapInfo; } @@ -226,7 +207,6 @@ public class VaultResponseSupport { /** * @return the warnings. */ - @Nullable public List getWarnings() { return this.warnings; } @@ -234,7 +214,7 @@ public class VaultResponseSupport { /** * @param warnings the warnings. */ - public void setWarnings(@Nullable List warnings) { + public void setWarnings(List warnings) { this.warnings = warnings; } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultToken.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultToken.java index d5ee3bfd..fcdaf3a5 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultToken.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultToken.java @@ -18,6 +18,9 @@ package org.springframework.vault.support; import java.util.Arrays; +import org.jspecify.annotations.Nullable; + +import org.springframework.lang.Contract; import org.springframework.util.Assert; /** @@ -42,7 +45,8 @@ public class VaultToken { * @param token must not be empty or {@literal null}. * @return the created {@link VaultToken} */ - public static VaultToken of(String token) { + @Contract("null -> fail") + public static VaultToken of(@Nullable String token) { Assert.hasText(token, "Token must not be empty"); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenRequest.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenRequest.java index 06e60b95..24276d12 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenRequest.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenRequest.java @@ -39,8 +39,7 @@ public class VaultTokenRequest { private static final VaultTokenRequest EMPTY = VaultTokenRequest.builder().build(); - @Nullable - private final String id; + private final @Nullable String id; private final List policies; @@ -54,25 +53,23 @@ public class VaultTokenRequest { private final boolean renewable; - @Nullable - private final String ttl; + private final @Nullable String ttl; @JsonProperty("explicit_max_ttl") - @Nullable - private final String explicitMaxTtl; + private final @Nullable String explicitMaxTtl; @JsonProperty("display_name") private final String displayName; @JsonProperty("entity_alias") - private final String entityAlias; + private final @Nullable String entityAlias; @JsonProperty("num_uses") private final int numUses; VaultTokenRequest(@Nullable String id, List policies, Map meta, boolean noParent, boolean noDefaultPolicy, boolean renewable, @Nullable String ttl, @Nullable String explicitMaxTtl, - String displayName, String entityAlias, int numUses) { + String displayName, @Nullable String entityAlias, int numUses) { this.id = id; this.policies = policies; @@ -174,7 +171,7 @@ public class VaultTokenRequest { * works in combination with role name. * @since 3.1 */ - public String getEntityAlias() { + public @Nullable String getEntityAlias() { return this.entityAlias; } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenResponse.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenResponse.java index 90a23573..e60d0b14 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenResponse.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTokenResponse.java @@ -27,7 +27,7 @@ public class VaultTokenResponse extends VaultResponse { * @return the {@link VaultToken}. */ public VaultToken getToken() { - return VaultToken.of((String) getRequiredAuth().get("client_token")); + return VaultToken.of((String) getAuth().get("client_token")); } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransformContext.java b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransformContext.java index 3b1365d1..2d9ec9e2 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransformContext.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/VaultTransformContext.java @@ -17,7 +17,8 @@ package org.springframework.vault.support; import java.util.Arrays; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/Versioned.java b/spring-vault-core/src/main/java/org/springframework/vault/support/Versioned.java index d50542d6..e168b5eb 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/Versioned.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/Versioned.java @@ -60,7 +60,7 @@ public class Versioned { private final @Nullable Metadata metadata; - private Versioned(T data, Version version) { + private Versioned(@Nullable T data, Version version) { this.version = version; this.metadata = null; @@ -345,7 +345,7 @@ public class Versioned { * {@literal null}. * @return {@code this} {@link MetadataBuilder}. */ - public MetadataBuilder deletedAt(Instant deletedAt) { + public MetadataBuilder deletedAt(@Nullable Instant deletedAt) { this.deletedAt = deletedAt; return this; } @@ -387,7 +387,7 @@ public class Versioned { * @return {@code this} {@link MetadataBuilder}. * @since 3.1 */ - public MetadataBuilder customMetadata(Map customMetadata) { + public MetadataBuilder customMetadata(@Nullable Map customMetadata) { this.customMetadata = customMetadata != null && !CollectionUtils.isEmpty(customMetadata) ? new LinkedHashMap<>(customMetadata) : null; diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/WrappedMetadata.java b/spring-vault-core/src/main/java/org/springframework/vault/support/WrappedMetadata.java index 21d8ef31..bc5d77bf 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/WrappedMetadata.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/WrappedMetadata.java @@ -34,7 +34,7 @@ public class WrappedMetadata { private final Instant creationTime; - private final String path; + private final @Nullable String path; private final Duration ttl; diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/lease/LeaseEndpointsUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/lease/LeaseEndpointsUnitTests.java index 97fd31b7..8cd6a6c3 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/core/lease/LeaseEndpointsUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/lease/LeaseEndpointsUnitTests.java @@ -66,7 +66,7 @@ public class LeaseEndpointsUnitTests { when(restOperations.exchange(eq("sys/renew"), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Map.class))) .thenReturn(new ResponseEntity<>(vaultResponseBody, HttpStatus.OK)); - when(oldLease.getLeaseId()).thenReturn("old_lease"); + when(oldLease.getRequiredLeaseId()).thenReturn("old_lease"); when(oldLease.getLeaseDuration()).thenReturn(Duration.ofSeconds(70)); Lease renewedLease = LeaseEndpoints.Legacy.renew(oldLease, restOperations); @@ -76,7 +76,7 @@ public class LeaseEndpointsUnitTests { Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); assertThat(actualRequestBodyParams).containsOnly(entry("lease_id", "old_lease"), entry("increment", "70")); - assertThat(renewedLease.getLeaseId()).isEqualTo("new_lease"); + assertThat(renewedLease.getRequiredLeaseId()).isEqualTo("new_lease"); assertThat(renewedLease.getLeaseDuration()).isEqualTo(Duration.ofSeconds(90)); assertThat(renewedLease.isRenewable()).isFalse(); @@ -87,7 +87,7 @@ public class LeaseEndpointsUnitTests { @DisplayName("LeaseEndpoints.Legacy uses PUT /sys/revoke to revoke a lease") void legacyRevokesUsingSysRevoke() { - when(oldLease.getLeaseId()).thenReturn("old_lease"); + when(oldLease.getRequiredLeaseId()).thenReturn("old_lease"); LeaseEndpoints.Legacy.revoke(oldLease, restOperations); @@ -112,7 +112,7 @@ public class LeaseEndpointsUnitTests { when(restOperations.exchange(eq("sys/leases/renew"), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Map.class))) .thenReturn(new ResponseEntity<>(vaultResponseBody, HttpStatus.OK)); - when(oldLease.getLeaseId()).thenReturn("old_lease"); + when(oldLease.getRequiredLeaseId()).thenReturn("old_lease"); when(oldLease.getLeaseDuration()).thenReturn(Duration.ofSeconds(70)); Lease renewedLease = LeaseEndpoints.Leases.renew(oldLease, restOperations); @@ -123,7 +123,7 @@ public class LeaseEndpointsUnitTests { Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); assertThat(actualRequestBodyParams).containsOnly(entry("lease_id", "old_lease"), entry("increment", "70")); - assertThat(renewedLease.getLeaseId()).isEqualTo("new_lease"); + assertThat(renewedLease.getRequiredLeaseId()).isEqualTo("new_lease"); assertThat(renewedLease.getLeaseDuration()).isEqualTo(Duration.ofSeconds(90)); assertThat(renewedLease.isRenewable()).isFalse(); @@ -135,7 +135,7 @@ public class LeaseEndpointsUnitTests { @DisplayName("LeaseEndpoints.SysLeases uses PUT /sys/leases/revoke to revoke a lease") void sysLeasesRevokesUsingSysLeasesRevoke() { - when(oldLease.getLeaseId()).thenReturn("old_lease"); + when(oldLease.getRequiredLeaseId()).thenReturn("old_lease"); LeaseEndpoints.Leases.revoke(oldLease, restOperations); @@ -159,7 +159,7 @@ public class LeaseEndpointsUnitTests { when(restOperations.exchange(eq("sys/leases/renew"), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Map.class))) .thenReturn(new ResponseEntity<>(vaultResponseBody, HttpStatus.OK)); - when(oldLease.getLeaseId()).thenReturn("old_lease"); + when(oldLease.getRequiredLeaseId()).thenReturn("old_lease"); when(oldLease.getLeaseDuration()).thenReturn(Duration.ofSeconds(70)); Lease renewedLease = LeaseEndpoints.Leases.renew(oldLease, restOperations); @@ -170,7 +170,7 @@ public class LeaseEndpointsUnitTests { Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); assertThat(actualRequestBodyParams).containsOnly(entry("lease_id", "old_lease"), entry("increment", "70")); - assertThat(renewedLease.getLeaseId()).isEqualTo("new_lease"); + assertThat(renewedLease.getRequiredLeaseId()).isEqualTo("new_lease"); assertThat(renewedLease.getLeaseDuration()).isEqualTo(Duration.ofSeconds(90)); assertThat(renewedLease.isRenewable()).isFalse(); @@ -181,7 +181,7 @@ public class LeaseEndpointsUnitTests { @DisplayName("LeaseEndpoints.Leases uses PUT /sys/leases/revoke to revoke a lease") void leasesRevokesUsingSysLeasesRevoke() { - when(oldLease.getLeaseId()).thenReturn("old_lease"); + when(oldLease.getRequiredLeaseId()).thenReturn("old_lease"); LeaseEndpoints.Leases.revoke(oldLease, restOperations); @@ -205,7 +205,7 @@ public class LeaseEndpointsUnitTests { when(restOperations.exchange(eq("sys/leases/renew"), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Map.class))) .thenReturn(new ResponseEntity<>(vaultResponseBody, HttpStatus.OK)); - when(oldLease.getLeaseId()).thenReturn("old_lease"); + when(oldLease.getRequiredLeaseId()).thenReturn("old_lease"); when(oldLease.getLeaseDuration()).thenReturn(Duration.ofSeconds(70)); Lease renewedLease = LeaseEndpoints.LeasesRevokedByPrefix.renew(oldLease, restOperations); @@ -216,7 +216,7 @@ public class LeaseEndpointsUnitTests { Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); assertThat(actualRequestBodyParams).containsOnly(entry("lease_id", "old_lease"), entry("increment", "70")); - assertThat(renewedLease.getLeaseId()).isEqualTo("new_lease"); + assertThat(renewedLease.getRequiredLeaseId()).isEqualTo("new_lease"); assertThat(renewedLease.getLeaseDuration()).isEqualTo(Duration.ofSeconds(90)); assertThat(renewedLease.isRenewable()).isFalse(); @@ -227,7 +227,7 @@ public class LeaseEndpointsUnitTests { @DisplayName("LeaseEndpoints.LeasesRevokedByPrefix uses PUT /sys/leases/revoke-prefix/{prefix} to revoke a lease") void leasesRevokedByPrefixRevokesUsingSysLeasesRevokePrefix() { - when(oldLease.getLeaseId()).thenReturn("my/old/lease"); + when(oldLease.getRequiredLeaseId()).thenReturn("my/old/lease"); LeaseEndpoints.LeasesRevokedByPrefix.revoke(oldLease, restOperations);