From fbbf9fca686b3192de4fa322a97a58fcabedda10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=A5sene?= Date: Tue, 29 Sep 2020 20:22:37 +0200 Subject: [PATCH] Enabled Vault clients to revoke leases using the revoke-prefix endpoint. - Added a new constant to LeaseEndpoints, SysLeasesUsingRevokePrefix - Adjusted the JavaDoc for LeaseEndpoints.SysLeases - Refactored common code to helper methods to reduce duplication Closes gh-588. Original pull request: gh-590. --- .../vault/core/lease/LeaseEndpoints.java | 60 ++++- .../core/lease/LeaseEndpointsUnitTests.java | 230 ++++++++++++++++++ 2 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 spring-vault-core/src/test/java/org/springframework/vault/core/lease/LeaseEndpointsUnitTests.java diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java index 3bb68443..e51fa4f6 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/lease/LeaseEndpoints.java @@ -64,8 +64,10 @@ public enum LeaseEndpoints { }, /** - * Sys/lease endpoints for Vault 0.8 and higher ({@literal /sys/leases/…}). + * Alias for {@link #Leases}. + * @deprecated since 2.3, use {@link #Leases} instead. */ + @Deprecated SysLeases { @Override @@ -75,6 +77,62 @@ public enum LeaseEndpoints { Map.class, lease.getLeaseId()); } + @Override + @SuppressWarnings("unchecked") + public Lease renew(Lease lease, RestOperations operations) { + + HttpEntity leaseRenewalEntity = getLeaseRenewalBody(lease); + + ResponseEntity> entity = (ResponseEntity) operations.exchange("sys/leases/renew", + HttpMethod.PUT, leaseRenewalEntity, Map.class); + + Assert.state(entity != null && entity.getBody() != null, "Renew response must not be null"); + + return toLease(entity.getBody()); + } + }, + + /** + * Sys/lease endpoints for Vault 0.8 and higher ({@literal /sys/leases/…}) that uses + * the {@literal /sys/leases/revoke} endpoint when revoking leases. + */ + Leases { + + @Override + public void revoke(Lease lease, RestOperations operations) { + + operations.exchange("sys/leases/revoke", HttpMethod.PUT, LeaseEndpoints.getLeaseRevocationBody(lease), + Map.class, lease.getLeaseId()); + } + + @Override + @SuppressWarnings("unchecked") + public Lease renew(Lease lease, RestOperations operations) { + + HttpEntity leaseRenewalEntity = getLeaseRenewalBody(lease); + + ResponseEntity> entity = (ResponseEntity) operations.exchange("sys/leases/renew", + HttpMethod.PUT, leaseRenewalEntity, Map.class); + + Assert.state(entity != null && entity.getBody() != null, "Renew response must not be null"); + + return toLease(entity.getBody()); + } + }, + + /** + * Sys/lease endpoints for Vault 0.8 and higher ({@literal /sys/leases/…}) that uses + * the {@literal /sys/leases/revoke-prefix/…} endpoint when revoking leases. + */ + LeasesRevokedByPrefix { + + @Override + public void revoke(Lease lease, RestOperations operations) { + + String endpoint = "sys/leases/revoke-prefix/" + lease.getLeaseId(); + operations.put(endpoint, null); + } + @Override @SuppressWarnings("unchecked") public Lease renew(Lease lease, RestOperations operations) { diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/lease/LeaseEndpointsUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/lease/LeaseEndpointsUnitTests.java new file mode 100644 index 00000000..917b0c5a --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/lease/LeaseEndpointsUnitTests.java @@ -0,0 +1,230 @@ +package org.springframework.vault.core.lease; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.vault.core.lease.domain.Lease; +import org.springframework.web.client.RestOperations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class LeaseEndpointsUnitTests { + + @Mock + Lease oldLease; + + @Mock + RestOperations restOperations; + + @Captor + ArgumentCaptor>> httpEntityCaptor; + + @Test + @DisplayName("LeaseEndpoints.Legacy uses PUT /sys/renew to renew a lease") + void legacyRenewsUsingSysRenew() { + + Map vaultResponseBody = new HashMap<>(); + vaultResponseBody.put("lease_id", "new_lease"); + vaultResponseBody.put("lease_duration", 90L); + vaultResponseBody.put("renewable", false); + 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.getLeaseDuration()).thenReturn(Duration.ofSeconds(70)); + + Lease renewedLease = LeaseEndpoints.Legacy.renew(oldLease, restOperations); + + verify(restOperations).exchange(eq("sys/renew"), eq(HttpMethod.PUT), httpEntityCaptor.capture(), eq(Map.class)); + + Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); + assertThat(actualRequestBodyParams).containsOnly( + entry("lease_id", "old_lease"), + entry("increment", "70")); + + assertThat(renewedLease.getLeaseId()).isEqualTo("new_lease"); + assertThat(renewedLease.getLeaseDuration()).isEqualTo(Duration.ofSeconds(90)); + assertThat(renewedLease.isRenewable()).isFalse(); + + verifyNoMoreInteractions(restOperations); + } + + @Test + @DisplayName("LeaseEndpoints.Legacy uses PUT /sys/revoke to revoke a lease") + void legacyRevokesUsingSysRevoke() { + + when(oldLease.getLeaseId()).thenReturn("old_lease"); + + LeaseEndpoints.Legacy.revoke(oldLease, restOperations); + + verify(restOperations).exchange(eq("sys/revoke"), eq(HttpMethod.PUT), httpEntityCaptor.capture(), + eq(Map.class), eq("old_lease")); + + Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); + assertThat(actualRequestBodyParams).containsOnly(entry("lease_id", "old_lease")); + + verifyNoMoreInteractions(restOperations); + } + + @Test + @SuppressWarnings("deprecation") + @DisplayName("LeaseEndpoints.SysLeases uses PUT /sys/leases/renew to renew a lease") + void sysLeasesRenewsUsingSysLeasesRenew() { + + Map vaultResponseBody = new HashMap<>(); + vaultResponseBody.put("lease_id", "new_lease"); + vaultResponseBody.put("lease_duration", 90L); + vaultResponseBody.put("renewable", false); + 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.getLeaseDuration()).thenReturn(Duration.ofSeconds(70)); + + Lease renewedLease = LeaseEndpoints.SysLeases.renew(oldLease, restOperations); + + verify(restOperations).exchange(eq("sys/leases/renew"), eq(HttpMethod.PUT), httpEntityCaptor.capture(), + eq(Map.class)); + + Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); + assertThat(actualRequestBodyParams).containsOnly( + entry("lease_id", "old_lease"), + entry("increment", "70")); + + assertThat(renewedLease.getLeaseId()).isEqualTo("new_lease"); + assertThat(renewedLease.getLeaseDuration()).isEqualTo(Duration.ofSeconds(90)); + assertThat(renewedLease.isRenewable()).isFalse(); + + verifyNoMoreInteractions(restOperations); + } + + @Test + @SuppressWarnings("deprecation") + @DisplayName("LeaseEndpoints.SysLeases uses PUT /sys/leases/revoke to revoke a lease") + void sysLeasesRevokesUsingSysLeasesRevoke() { + + when(oldLease.getLeaseId()).thenReturn("old_lease"); + + LeaseEndpoints.SysLeases.revoke(oldLease, restOperations); + + verify(restOperations).exchange(eq("sys/leases/revoke"), eq(HttpMethod.PUT), httpEntityCaptor.capture(), + eq(Map.class), eq("old_lease")); + + Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); + assertThat(actualRequestBodyParams).containsOnly(entry("lease_id", "old_lease")); + + verifyNoMoreInteractions(restOperations); + } + + @Test + @DisplayName("LeaseEndpoints.Leases uses PUT /sys/leases/renew to renew a lease") + void leasesRenewsUsingSysLeasesRenew() { + + Map vaultResponseBody = new HashMap<>(); + vaultResponseBody.put("lease_id", "new_lease"); + vaultResponseBody.put("lease_duration", 90L); + vaultResponseBody.put("renewable", false); + 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.getLeaseDuration()).thenReturn(Duration.ofSeconds(70)); + + Lease renewedLease = LeaseEndpoints.Leases.renew(oldLease, restOperations); + + verify(restOperations).exchange(eq("sys/leases/renew"), eq(HttpMethod.PUT), httpEntityCaptor.capture(), + eq(Map.class)); + + Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); + assertThat(actualRequestBodyParams).containsOnly( + entry("lease_id", "old_lease"), + entry("increment", "70")); + + assertThat(renewedLease.getLeaseId()).isEqualTo("new_lease"); + assertThat(renewedLease.getLeaseDuration()).isEqualTo(Duration.ofSeconds(90)); + assertThat(renewedLease.isRenewable()).isFalse(); + + verifyNoMoreInteractions(restOperations); + } + + @Test + @DisplayName("LeaseEndpoints.Leases uses PUT /sys/leases/revoke to revoke a lease") + void leasesRevokesUsingSysLeasesRevoke() { + + when(oldLease.getLeaseId()).thenReturn("old_lease"); + + LeaseEndpoints.Leases.revoke(oldLease, restOperations); + + verify(restOperations).exchange(eq("sys/leases/revoke"), eq(HttpMethod.PUT), httpEntityCaptor.capture(), + eq(Map.class), eq("old_lease")); + + Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); + assertThat(actualRequestBodyParams).containsOnly(entry("lease_id", "old_lease")); + + verifyNoMoreInteractions(restOperations); + } + + @Test + @DisplayName("LeaseEndpoints.LeasesRevokedByPrefix uses PUT /sys/leases/renew to renew a lease") + void leasesRevokedByPrefixRenewsUsingSysLeasesRenew() { + + Map vaultResponseBody = new HashMap<>(); + vaultResponseBody.put("lease_id", "new_lease"); + vaultResponseBody.put("lease_duration", 90L); + vaultResponseBody.put("renewable", false); + 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.getLeaseDuration()).thenReturn(Duration.ofSeconds(70)); + + Lease renewedLease = LeaseEndpoints.LeasesRevokedByPrefix.renew(oldLease, restOperations); + + verify(restOperations).exchange(eq("sys/leases/renew"), eq(HttpMethod.PUT), httpEntityCaptor.capture(), + eq(Map.class)); + + Map actualRequestBodyParams = httpEntityCaptor.getValue().getBody(); + assertThat(actualRequestBodyParams).containsOnly( + entry("lease_id", "old_lease"), + entry("increment", "70")); + + assertThat(renewedLease.getLeaseId()).isEqualTo("new_lease"); + assertThat(renewedLease.getLeaseDuration()).isEqualTo(Duration.ofSeconds(90)); + assertThat(renewedLease.isRenewable()).isFalse(); + + verifyNoMoreInteractions(restOperations); + } + + @Test + @DisplayName("LeaseEndpoints.LeasesRevokedByPrefix uses PUT /sys/leases/revoke-prefix/{prefix} to revoke a lease") + void leasesRevokedByPrefixRevokesUsingSysLeasesRevokePrefix() { + + when(oldLease.getLeaseId()).thenReturn("my/old/lease"); + + LeaseEndpoints.LeasesRevokedByPrefix.revoke(oldLease, restOperations); + + verify(restOperations).put("sys/leases/revoke-prefix/my/old/lease", null); + + verifyNoMoreInteractions(restOperations); + } +}