diff --git a/spring-vault-core/src/main/java/org/springframework/vault/repository/mapping/BasicVaultPersistentEntity.java b/spring-vault-core/src/main/java/org/springframework/vault/repository/mapping/BasicVaultPersistentEntity.java index c54e5399..37b19e5c 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/repository/mapping/BasicVaultPersistentEntity.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/repository/mapping/BasicVaultPersistentEntity.java @@ -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 extends BasicKeyValuePersistentEntity implements VaultPersistentEntity { - 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 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); } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/repository/mapping/Secret.java b/spring-vault-core/src/main/java/org/springframework/vault/repository/mapping/Secret.java index e9c1b800..8cd6e7ed 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/repository/mapping/Secret.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/repository/mapping/Secret.java @@ -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"; } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/repository/mapping/BasicVaultPersistentEntityUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/repository/mapping/BasicVaultPersistentEntityUnitTests.java index 6010a28b..6f33df4d 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/repository/mapping/BasicVaultPersistentEntityUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/repository/mapping/BasicVaultPersistentEntityUnitTests.java @@ -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 getProperties() { + + Map properties = new LinkedHashMap<>(); + properties.put("myProperty", "collectionName"); + properties.put("myKeySpace", "foo"); + return properties; + } + } } diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index e8f31872..ef0601a0 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -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