Use JSpecify to indicate nullness.

Closes #902
This commit is contained in:
Mark Paluch
2025-02-19 15:44:32 +01:00
parent ebdeaf0592
commit 20a4d0896f
67 changed files with 568 additions and 390 deletions

View File

@@ -383,7 +383,6 @@
<configuration>
<release>${java.version}</release>
<parameters>true</parameters>
<release>${source.level}</release>
<showWarnings>true</showWarnings>
<annotationProcessorPaths>
<path>

View File

@@ -214,7 +214,7 @@ public class AppRoleAuthentication implements ClientAuthentication, Authenticati
ResponseEntity<VaultResponse> 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) {

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -92,7 +92,7 @@ public class AuthenticationStepsExecutor implements ClientAuthentication {
}
@SuppressWarnings({ "unchecked", "ConstantConditions" })
private Object evaluate(Iterable<Node<?>> steps) {
private @Nullable Object evaluate(Iterable<Node<?>> 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<Object, Object>) o, state);
}
if (o instanceof ZipStep) {
Assert.state(state != null, "No state available for ZipStep");
state = doZipStep((ZipStep<Object, Object>) o, state);
}
if (o instanceof OnNextStep) {
Assert.state(state != null, "No state available for OnNextStep");
state = doOnNext((OnNextStep<Object>) o, state);
}
@@ -143,12 +146,11 @@ public class AuthenticationStepsExecutor implements ClientAuthentication {
}
@SuppressWarnings("ConstantConditions")
@Nullable
private Object doHttpRequest(HttpRequestNode<Object> step, @Nullable Object state) {
private @Nullable Object doHttpRequest(HttpRequestNode<Object> step, @Nullable Object state) {
HttpRequest<Object> 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<Object, Object> o, Object state) {
Object result = evaluate(o.getRight());

View File

@@ -155,7 +155,7 @@ public class AuthenticationStepsOperator implements VaultTokenSupplier {
return state;
}
@SuppressWarnings({"NullAway", "DataFlowIssue"})
@SuppressWarnings({ "NullAway", "DataFlowIssue" })
private Mono<Object> doHttpRequest(HttpRequestNode<Object> step, Object state) {
HttpRequest<Object> definition = step.getDefinition();

View File

@@ -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<Map> 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<Map> 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<String, Object> instanceMetadata) {
Map<String, String> 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);
}

View File

@@ -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<Mono<VaultToken>> tokenRef = new AtomicReference<>(EMPTY);
private final AtomicReference<@NonNull Mono<VaultToken>> 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<VaultToken> getVaultToken() throws VaultException {
if (Objects.equals(this.tokenRef.get(), EMPTY)) {

View File

@@ -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)) {

View File

@@ -206,8 +206,7 @@ public class LoginToken extends VaultToken {
*/
public static class LoginTokenBuilder {
private char @Nullable[] token;
private char @Nullable [] token;
private boolean renewable;

View File

@@ -40,8 +40,8 @@ final class LoginTokenUtil {
static LoginToken from(Map<String, Object> 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<String, ?> auth) {
Assert.notNull(auth, "Authentication must not be null");

View File

@@ -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<Mono<TokenWrapper>> token = new AtomicReference<>(EMPTY);
private final AtomicReference<@NonNull Mono<TokenWrapper>> 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<TokenWrapper> 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<Void> 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<VaultToken> 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<VaultToken> getVaultToken() throws VaultException {
Mono<TokenWrapper> tokenWrapper = this.token.get();

View File

@@ -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<? extends VaultResponseSupport<Map<String, Object>>> response,
String key) {
return getRequiredValue(getRequiredBody(response), key);
}
public static Object getRequiredValue(@Nullable VaultResponseSupport<Map<String, Object>> 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;
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.vault.support.VaultResponse;
* @author Mark Paluch
* @since 2.2
*/
@SuppressWarnings("NullAway")
public enum UnwrappingEndpoints {
/**

View File

@@ -90,6 +90,7 @@ public class UsernamePasswordAuthentication implements ClientAuthentication, Aut
return createAuthenticationSteps(this.options);
}
@SuppressWarnings("NullAway")
private VaultToken createTokenUsingUsernamePasswordAuthentication() {
try {

View File

@@ -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();
}

View File

@@ -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<String, String> defaultHeaders = new LinkedHashMap<>(this.defaultHeaders);

View File

@@ -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));
}
/**

View File

@@ -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 {

View File

@@ -35,7 +35,7 @@ class KeyValueUtilities {
static Metadata getMetadata(Map<String, Object> 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<String, Object> responseMetadata, String key) {
private static @Nullable TemporalAccessor getDate(Map<String, Object> 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<String, Object> 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<String, Object> 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<Metadata> buildVersions(Map<String, Map<String, Object>> 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<Metadata> buildVersions(@Nullable Map<String, Map<String, Object>> 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<String, Object> 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<String, Object> createPatchRequest(Map<String, ?> patch, Map<String, Object> previous,

View File

@@ -84,7 +84,7 @@ class PropertyMapper {
* @return a {@link Source} that can be used to complete the mapping
* @see #from(Object)
*/
public <T> Source<T> from(Supplier<T> supplier) {
public <T> Source<T> from(Supplier<@Nullable T> supplier) {
Assert.notNull(supplier, "Supplier must not be null");
Source<T> 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<T>(Supplier<T> supplier) implements Supplier<T> {
private record NullPointerExceptionSafeSupplier<T>(Supplier<T> supplier) implements Supplier<@Nullable T> {
@Override
public T get() {
public @Nullable T get() {
try {
return this.supplier.get();
}

View File

@@ -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 -> {

View File

@@ -47,6 +47,7 @@ public class ReactiveVaultSysTemplate implements ReactiveVaultSysOperations {
}
@Override
@SuppressWarnings("NullAway")
public Mono<Boolean> 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<VaultHealth> 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);
});
});
}

View File

@@ -25,14 +25,13 @@ import org.springframework.web.client.RestOperations;
* @author Mark Paluch
*/
@FunctionalInterface
public interface RestOperationsCallback<T> {
public interface RestOperationsCallback<T extends @Nullable Object> {
/**
* 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);
}

View File

@@ -54,15 +54,13 @@ class VaultKeyValue1Template extends VaultKeyValueAccessor implements VaultKeyVa
this.path = path;
}
@Nullable
@Override
public List<String> list(String path) {
public @Nullable List<String> 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 <T> VaultResponseSupport<T> get(String path, Class<T> responseType) {
public <T> @Nullable VaultResponseSupport<T> get(String path, Class<T> responseType) {
Assert.hasText(path, "Path must not be empty");
Assert.notNull(responseType, "Response type must not be null");

View File

@@ -48,10 +48,9 @@ abstract class VaultKeyValue2Accessor extends VaultKeyValueAccessor {
this.path = path;
}
@Nullable
@Override
@SuppressWarnings("unchecked")
public List<String> list(String path) {
public @Nullable List<String> list(String path) {
VaultListResponse read = doRead(restOperations -> {
return restOperations.exchange(

View File

@@ -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 <T> VaultResponseSupport<T> get(String path, Class<T> responseType) {
public <T> @Nullable VaultResponseSupport<T> get(String path, Class<T> 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));
}

View File

@@ -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 <T> return type. Value is created by the {@code mappingFunction}.
* @return mapped value.
*/
@Nullable
<I, T> T doRead(String path, Class<I> deserializeAs, BiFunction<VaultResponseSupport<?>, I, T> mappingFunction) {
<I, T> @Nullable T doRead(String path, Class<I> deserializeAs,
BiFunction<VaultResponseSupport<?>, I, T> mappingFunction) {
ParameterizedTypeReference<VaultResponseSupport<JsonNode>> 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> T doRead(String path, ParameterizedTypeReference<T> typeReference) {
<T> @Nullable T doRead(String path, ParameterizedTypeReference<T> 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> T doRead(Function<RestOperations, ResponseEntity<T>> callback) {
@SuppressWarnings("NullAway")
<T extends @Nullable Object> @Nullable T doRead(Function<RestOperations, ResponseEntity<T>> 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);
}
}

View File

@@ -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<Map> response = this.vaultOperations.read(getPath(path), Map.class);

View File

@@ -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
<T> VaultResponseSupport<T> get(String path, Class<T> responseType);
<T> @Nullable VaultResponseSupport<T> get(String path, Class<T> responseType);
/**
* Update the secret at {@code path} without removing the existing secrets. Requires a

View File

@@ -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
<T> VaultResponseSupport<T> read(String path, Class<T> responseType);
<T extends @Nullable Object> VaultResponseSupport<T> read(String path, Class<T> 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 <T> VaultResponseSupport<T> readRequired(String path, Class<T> responseType) {
VaultResponseSupport<T> 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<String> 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> T doWithVault(RestOperationsCallback<T> clientCallback) throws VaultException, RestClientException;
<T extends @Nullable Object> T doWithVault(RestOperationsCallback<T> 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> T doWithSession(RestOperationsCallback<T> sessionCallback) throws VaultException, RestClientException;
<T extends @Nullable Object> T doWithSession(RestOperationsCallback<T> sessionCallback)
throws VaultException, RestClientException;
}

View File

@@ -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> T requestCertificate(String roleName, String requestPath, Map<String, Object> request,
Class<T> 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");

View File

@@ -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<String> getPolicyNames() throws VaultException {
return requireResponse(
(List<String>) this.vaultOperations.read("sys/policy").getRequiredData().get("policies"));
(List<String>) 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<VaultResponse> 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<Void> {
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<Map<String, VaultMount>> {
private Map<String, VaultMount> topLevelMounts = new HashMap<>();
private final Map<String, VaultMount> topLevelMounts = new HashMap<>();
@JsonIgnore
public Map<String, VaultMount> 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<VaultHealthImpl> healthResponse = restOperations.exchange("sys/health", HttpMethod.GET,
emptyNamespace(null), VaultHealthImpl.class);
return healthResponse.getBody();
return requireResponse(healthResponse.getBody());
}
catch (RestClientResponseException responseError) {

View File

@@ -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 <T> VaultResponseSupport<T> read(String path, Class<T> responseType) {
@SuppressWarnings("NullAway")
public <T> @Nullable VaultResponseSupport<T> read(String path, Class<T> responseType) {
ParameterizedTypeReference<VaultResponseSupport<T>> ref = VaultResponses.getTypeReference(responseType);
@@ -378,13 +379,13 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
@Override
@SuppressWarnings("unchecked")
@Nullable
public List<String> list(String path) {
public @Nullable List<String> 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> T doWithVault(RestOperationsCallback<T> clientCallback) {
public <T extends @Nullable Object> T doWithVault(RestOperationsCallback<T> clientCallback) {
Assert.notNull(clientCallback, "Client callback must not be null");
@@ -438,7 +440,7 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
}
@Override
public <T> T doWithSession(RestOperationsCallback<T> sessionCallback) {
public <T extends @Nullable Object> T doWithSession(RestOperationsCallback<T> sessionCallback) {
Assert.notNull(sessionCallback, "Session callback must not be null");
@@ -450,10 +452,10 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
}
}
@Nullable
private <T> T doRead(String path, Class<T> responseType) {
@SuppressWarnings("NullAway")
private <T extends @Nullable Object> T doRead(String path, Class<T> responseType) {
return doWithSession(restOperations -> {
return doWithSession((RestOperations restOperations) -> {
try {
return restOperations.getForObject(path, responseType);

View File

@@ -101,12 +101,13 @@ public class VaultTokenTemplate implements VaultTokenOperations {
writeToken("auth/token/revoke-orphan", vaultToken, VaultTokenResponse.class);
}
@SuppressWarnings("NullAway")
private <T extends VaultResponseSupport<?>> T writeAndReturn(String path, @Nullable Object body,
Class<T> responseType) {
Assert.hasText(path, "Path must not be empty");
T response = this.vaultOperations.doWithSession(restOperations -> {
return this.vaultOperations.doWithSession(restOperations -> {
try {
ResponseEntity<T> 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,

View File

@@ -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<String, Object> data = this.vaultOperations.write("%s/encode/%s".formatted(this.path, roleName), request)
Map<String, Object> 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<String, String> 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<String, ?> 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<Map<String, String>> getBatchData(VaultResponse vaultResponse) {
return (List<Map<String, String>>) vaultResponse.getRequiredData().get("batch_results");
}
private static <T> T requireResponse(@Nullable T response) {
Assert.state(response != null, "Response must not be null");
return response;
}
}

View File

@@ -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<String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, Object> 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<String, Object> 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<String, Object> request = toRequestBody(verificationRequest);
Map<String, Object> response = this.vaultOperations.write("%s/verify/%s".formatted(this.path, keyName), request)
.getRequiredData();
Map<String, Object> 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<String, Object> 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<String, Object> toRequestBody(VaultSignatureVerificationRequest verificationRequest) {
Map<String, Object> request = new LinkedHashMap<>(5);
@@ -483,6 +485,7 @@ public class VaultTransitTemplate implements VaultTransitOperations {
}
}
@SuppressWarnings("NullAway")
static <T> List<VaultEncryptionResult> toBatchResults(VaultResponse vaultResponse, List<T> batchRequests,
Function<T, VaultTransitContext> 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<Map<String, String>> getBatchData(VaultResponse vaultResponse) {
return (List<Map<String, String>>) 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<String, String> 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)

View File

@@ -65,7 +65,7 @@ public class VaultVersionedKeyValueTemplate extends VaultKeyValue2Accessor imple
@Nullable
@Override
@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked", "rawtypes" })
public Versioned<Map<String, Object>> 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 <T> Versioned<T> doRead(String path, Version version, Class<T> 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());

View File

@@ -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 extends VaultResponseSupport<?>> T doUnwrap(VaultToken token,
BiFunction<RestOperations, HttpEntity<?>, T> requestFunction) {
@SuppressWarnings("NullAway")
private <T extends VaultResponseSupport<?>> @Nullable T doUnwrap(VaultToken token,
BiFunction<RestOperations, HttpEntity<?>, @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<String, String> 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<String, ?> 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<String, ?> 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");
}
}

View File

@@ -199,7 +199,7 @@ public class LeaseAwareVaultPropertySource extends EnumerablePropertySource<Vaul
}
@Override
public Object getProperty(String name) {
public @Nullable Object getProperty(String name) {
return this.properties.get(name);
}
@@ -207,7 +207,7 @@ public class LeaseAwareVaultPropertySource extends EnumerablePropertySource<Vaul
public String[] getPropertyNames() {
Set<String> strings = this.properties.keySet();
return strings.toArray(new String[strings.size()]);
return strings.toArray(new String[0]);
}
// -------------------------------------------------------------------------

View File

@@ -185,7 +185,7 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
}
@Override
public Object getProperty(String name) {
public @Nullable Object getProperty(String name) {
return this.properties.get(name);
}
@@ -193,7 +193,7 @@ public class VaultPropertySource extends EnumerablePropertySource<VaultOperation
public String[] getPropertyNames() {
Set<String> 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<VaultOperation
* @return the resulting {@link Map} or {@literal null} if properties were not found.
* @throws VaultException on problems retrieving properties
*/
@Nullable
protected Map<String, Object> doGetProperties(String path) throws VaultException {
protected @Nullable Map<String, Object> doGetProperties(String path) throws VaultException {
VaultResponse vaultResponse;

View File

@@ -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<String, Object> body) {
String leaseId = (String) body.get("lease_id");
@@ -141,7 +142,7 @@ public enum LeaseEndpoints {
private static HttpEntity<Object> getLeaseRenewalBody(Lease lease) {
Map<String, String> 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<Object> getLeaseRevocationBody(Lease lease) {
Map<String, String> leaseRenewalData = new HashMap<>();
leaseRenewalData.put("lease_id", lease.getLeaseId());
leaseRenewalData.put("lease_id", lease.getRequiredLeaseId());
return new HttpEntity<>(leaseRenewalData);
}

View File

@@ -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");
}

View File

@@ -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.
*/

View File

@@ -48,6 +48,7 @@ public class SecretLeaseRotatedEvent extends SecretLeaseCreatedEvent {
return this.previousLease;
}
@SuppressWarnings("NullAway")
public Lease getCurrentLease() {
return getLease();
}

View File

@@ -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));

View File

@@ -82,7 +82,7 @@ public class DefaultVaultTypeMapper extends DefaultTypeMapper<Map<String, Object
}
private DefaultVaultTypeMapper(@Nullable String typeKey, TypeAliasAccessor<Map<String, Object>> accessor,
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext,
@Nullable MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext,
List<? extends TypeInformationMapper> mappers) {
super(accessor, mappingContext, mappers);

View File

@@ -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<? extends S> 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<S>) 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<VaultPersistentProperty> getParameterProvider(VaultPersistentEntity<?> entity,
@@ -152,9 +161,8 @@ public class MappingVaultConverter extends AbstractVaultConverter {
return new ParameterValueProvider<VaultPersistentProperty>() {
@Nullable
@Override
public <T> T getParameterValue(Parameter<T, VaultPersistentProperty> parameter) {
public <T> @Nullable T getParameterValue(Parameter<T, VaultPersistentProperty> 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<Object> 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<Object, Object> readMap(TypeInformation<?> type, Map<String, Object> sourceMap) {
protected Map<@Nullable Object, @Nullable Object> readMap(TypeInformation<?> type, Map<String, Object> 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<Object, Object> map = CollectionFactory.createMap(mapType, rawKeyType, sourceMap.keySet().size());
Map<@Nullable Object, @Nullable Object> map = CollectionFactory.createMap(mapType, rawKeyType,
sourceMap.keySet().size());
for (Entry<String, Object> 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<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type,
private List<Object> writeCollectionInternal(Collection<@Nullable ?> source, @Nullable TypeInformation<?> type,
List<Object> 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;

View File

@@ -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;
}

View File

@@ -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<String, Object> getAsMap(Object source) {
private @Nullable static Map<String, Object> getAsMap(@Nullable Object source) {
if (source instanceof Map) {
return (Map<String, Object>) source;

View File

@@ -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()),

View File

@@ -64,8 +64,7 @@ public abstract class AbstractResult<V> {
* 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<V> {
* @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<V> {
/**
* @return the actual result if this result completed successfully.
*/
@Nullable
protected abstract V get0();
protected abstract @Nullable V get0();
}

View File

@@ -70,7 +70,7 @@ public class CertificateBundle extends Certificate {
@JsonProperty("certificate") String certificate, @JsonProperty("issuing_ca") String issuingCaCertificate,
@JsonProperty("ca_chain") List<String> 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;

View File

@@ -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;
};
}

View File

@@ -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");

View File

@@ -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");

View File

@@ -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;
}

View File

@@ -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<String, String> customMetadata;
private final Duration deleteVersionAfter;
private final @Nullable Duration deleteVersionAfter;
private final int maxVersions;
@@ -54,8 +55,8 @@ public class VaultMetadataResponse {
private final List<Versioned.Metadata> versions;
private VaultMetadataResponse(boolean casRequired, Instant createdTime, int currentVersion,
Map<String, String> customMetadata, Duration deleteVersionAfter, int maxVersions, int oldestVersion,
Instant updatedTime, List<Metadata> versions) {
Map<String, String> customMetadata, @Nullable Duration deleteVersionAfter, int maxVersions,
int oldestVersion, Instant updatedTime, List<Metadata> 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<String, String> customMetadata;
private Map<String, String> 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<Versioned.Metadata> versions;
private List<Versioned.Metadata> 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);

View File

@@ -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<String, Object> 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;

View File

@@ -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<T> {
@Nullable
private Map<String, Object> auth;
private Map<String, Object> auth = Collections.emptyMap();
@Nullable
private T data;
@Nullable
private Map<String, Object> metadata;
private Map<String, Object> metadata = Collections.emptyMap();
@JsonProperty("wrap_info")
@Nullable
private Map<String, String> wrapInfo;
private Map<String, String> wrapInfo = Collections.emptyMap();
@JsonProperty("lease_duration")
private long leaseDuration;
@@ -59,8 +57,7 @@ public class VaultResponseSupport<T> {
private boolean renewable;
@Nullable
private List<String> warnings;
private List<String> warnings = Collections.emptyList();
/**
* Apply metadata such as auth or warnings without copying data.
@@ -82,28 +79,14 @@ public class VaultResponseSupport<T> {
/**
* @return authentication payload.
*/
@Nullable
public Map<String, Object> getAuth() {
return this.auth;
}
/**
* @return the authentication payload.
* @throws IllegalStateException if {@code auth} is null.
*/
public Map<String, Object> 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<String, Object> auth) {
public void setAuth(Map<String, Object> auth) {
this.auth = auth;
}
@@ -138,7 +121,6 @@ public class VaultResponseSupport<T> {
/**
* @return request metadata.
*/
@Nullable
public Map<String, Object> getMetadata() {
return this.metadata;
}
@@ -146,7 +128,7 @@ public class VaultResponseSupport<T> {
/**
* @param metadata request metadata.
*/
public void setMetadata(@Nullable Map<String, Object> metadata) {
public void setMetadata(Map<String, Object> metadata) {
this.metadata = metadata;
}
@@ -196,7 +178,6 @@ public class VaultResponseSupport<T> {
/**
* @return response wrapping details.
*/
@Nullable
public Map<String, String> getWrapInfo() {
return this.wrapInfo;
}
@@ -204,7 +185,7 @@ public class VaultResponseSupport<T> {
/**
* @param wrapInfo response wrapping details.
*/
public void setWrapInfo(@Nullable Map<String, String> wrapInfo) {
public void setWrapInfo(Map<String, String> wrapInfo) {
this.wrapInfo = wrapInfo;
}
@@ -226,7 +207,6 @@ public class VaultResponseSupport<T> {
/**
* @return the warnings.
*/
@Nullable
public List<String> getWarnings() {
return this.warnings;
}
@@ -234,7 +214,7 @@ public class VaultResponseSupport<T> {
/**
* @param warnings the warnings.
*/
public void setWarnings(@Nullable List<String> warnings) {
public void setWarnings(List<String> warnings) {
this.warnings = warnings;
}

View File

@@ -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");

View File

@@ -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<String> 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<String> policies, Map<String, String> 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;
}

View File

@@ -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"));
}
}

View File

@@ -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;

View File

@@ -60,7 +60,7 @@ public class Versioned<T> {
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<T> {
* {@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<T> {
* @return {@code this} {@link MetadataBuilder}.
* @since 3.1
*/
public MetadataBuilder customMetadata(Map<String, String> customMetadata) {
public MetadataBuilder customMetadata(@Nullable Map<String, String> customMetadata) {
this.customMetadata = customMetadata != null && !CollectionUtils.isEmpty(customMetadata)
? new LinkedHashMap<>(customMetadata) : null;

View File

@@ -34,7 +34,7 @@ public class WrappedMetadata {
private final Instant creationTime;
private final String path;
private final @Nullable String path;
private final Duration ttl;

View File

@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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);