Add SpEL support for @Secret annotation.

We now support SpEL expressions in the Secret annotation.

@Secret(backend = "#{myBackend}", value = "#{myKeySpace}")
class ExpressionEntity {
    // …
}

Closes gh-436.
This commit is contained in:
Mark Paluch
2019-06-19 14:48:22 +02:00
parent 3be195d9d9
commit 38f6759855
4 changed files with 99 additions and 14 deletions

View File

@@ -18,6 +18,11 @@ package org.springframework.vault.repository.mapping;
import org.springframework.data.keyvalue.core.mapping.BasicKeyValuePersistentEntity;
import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
@@ -30,9 +35,13 @@ public class BasicVaultPersistentEntity<T> extends
BasicKeyValuePersistentEntity<T, VaultPersistentProperty> implements
VaultPersistentEntity<T> {
private final String keyspace;
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private final String secretBackend;
private final @Nullable String backend;
private final @Nullable Expression backendExpression;
private final @Nullable String keyspace;
private final @Nullable Expression keyspaceExpression;
/**
* Creates new {@link BasicVaultPersistentEntity}.
@@ -46,26 +55,55 @@ public class BasicVaultPersistentEntity<T> extends
Secret annotation = findAnnotation(Secret.class);
String keyspace = super.getKeySpace();
String secretBackend = "secret";
if (annotation != null && StringUtils.hasText(annotation.backend())) {
if (annotation != null) {
if (StringUtils.hasText(annotation.backend())) {
secretBackend = annotation.backend();
}
this.backend = annotation.backend();
this.backendExpression = detectExpression(this.backend);
this.keyspace = super.getKeySpace();
this.keyspaceExpression = detectExpression(this.keyspace);
}
else {
this.backend = "secret";
this.backendExpression = null;
this.secretBackend = secretBackend;
this.keyspace = String.format("%s/%s", secretBackend, keyspace);
this.keyspace = super.getKeySpace();
this.keyspaceExpression = null;
}
}
/**
* Returns a SpEL {@link Expression} if the given {@link String} is actually an
* expression that does not evaluate to a {@link LiteralExpression} (indicating that
* no subsequent evaluation is necessary).
*
* @param potentialExpression can be {@literal null}
* @return
*/
@Nullable
private static Expression detectExpression(String potentialExpression) {
Expression expression = PARSER.parseExpression(potentialExpression,
ParserContext.TEMPLATE_EXPRESSION);
return expression instanceof LiteralExpression ? null : expression;
}
@Override
public String getKeySpace() {
return keyspace;
return String.format("%s/%s", getSecretBackend(), doGetKeySpace());
}
private String doGetKeySpace() {
return keyspaceExpression == null //
? keyspace //
: keyspaceExpression.getValue(getEvaluationContext(null), String.class);
}
@Override
public String getSecretBackend() {
return secretBackend;
return backendExpression == null //
? backend //
: backendExpression.getValue(getEvaluationContext(null), String.class);
}
}

View File

@@ -40,14 +40,17 @@ import org.springframework.data.keyvalue.annotation.KeySpace;
public @interface Secret {
/**
* The prefix to distinguish between domain types.
* The prefix to distinguish between domain types. The attribute supports SpEL
* expressions to dynamically calculate the keyspace based on a per-operation basis.
*
* @see KeySpace
*/
@AliasFor(annotation = KeySpace.class, attribute = "value")
String value() default "";
/**
* Secret backend mount, defaults to {@literal secret}.
* Secret backend mount, defaults to {@literal secret}. The attribute supports SpEL
* expressions to dynamically calculate the backend based on a per-operation basis.
*/
String backend() default "secret";
}

View File

@@ -15,9 +15,15 @@
*/
package org.springframework.vault.repository.mapping;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import static org.assertj.core.api.Assertions.assertThat;
@@ -48,6 +54,21 @@ public class BasicVaultPersistentEntityUnitTests {
assertThat(persistentEntity.getIdProperty()).isNotNull();
}
@Test
public void shouldEvaluateExpression() {
VaultPersistentEntity<?> persistentEntity = mappingContext
.getPersistentEntity(ExpressionEntity.class);
persistentEntity
.setEvaluationContextProvider(new ExtensionAwareEvaluationContextProvider(
Collections.singletonList(new SampleExtension())));
assertThat(persistentEntity.getSecretBackend()).isEqualTo("collectionName");
assertThat(persistentEntity.getKeySpace()).isEqualTo("collectionName/foo");
}
static class IdProperty {
String id, username;
}
@@ -57,4 +78,26 @@ public class BasicVaultPersistentEntityUnitTests {
String username;
}
@Secret(backend = "#{myProperty}", value = "#{myKeySpace}")
static class ExpressionEntity {
@Id
String username;
}
static class SampleExtension implements EvaluationContextExtension {
@Override
public String getExtensionId() {
return "sampleExtension";
}
@Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new LinkedHashMap<>();
properties.put("myProperty", "collectionName");
properties.put("myKeySpace", "foo");
return properties;
}
}
}

View File

@@ -4,6 +4,7 @@
[[new-features.2-2-0]]
=== What's new in Spring Vault 2.2
* Support for Key-Value v2 (versioned backend) secrets through `@VaultPropertySource`.
* SpEL support in `@Secret`.
[[new-features.2-1-0]]
=== What's new in Spring Vault 2.1