diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ApiKeyDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ApiKeyDto.java index 90b4165cfd..b07e329a8a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ApiKeyDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ApiKeyDto.java @@ -31,6 +31,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import javax.validation.constraints.NotEmpty; +import java.time.Instant; @Getter @Setter @@ -40,6 +41,7 @@ public class ApiKeyDto extends HalRepresentation { private String displayName; @NotEmpty private String permissionRole; + private Instant created; public ApiKeyDto(Links links) { super(links); diff --git a/scm-webapp/src/main/java/sonia/scm/security/ApiKey.java b/scm-webapp/src/main/java/sonia/scm/security/ApiKey.java index 9576fb8201..f2d8bfd046 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ApiKey.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ApiKey.java @@ -27,14 +27,22 @@ package sonia.scm.security; import lombok.AllArgsConstructor; import lombok.Getter; +import java.time.Instant; + @Getter @AllArgsConstructor public class ApiKey { private final String id; private final String displayName; private final String permissionRole; + private final Instant created; ApiKey(ApiKeyWithPassphrase apiKeyWithPassphrase) { - this(apiKeyWithPassphrase.getId(), apiKeyWithPassphrase.getDisplayName(), apiKeyWithPassphrase.getPermissionRole()); + this( + apiKeyWithPassphrase.getId(), + apiKeyWithPassphrase.getDisplayName(), + apiKeyWithPassphrase.getPermissionRole(), + apiKeyWithPassphrase.getCreated() + ); } } diff --git a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyService.java b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyService.java index 631c7aff3a..9a358f0832 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyService.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyService.java @@ -43,6 +43,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.function.Supplier; import java.util.stream.Stream; +import static java.time.Instant.now; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; import static org.apache.commons.lang.RandomStringUtils.random; @@ -79,7 +80,7 @@ public class ApiKeyService { String passphrase = passphraseGenerator.get(); String hashedPassphrase = passwordService.encryptPassword(passphrase); final String id = keyGenerator.createKey(); - ApiKeyWithPassphrase key = new ApiKeyWithPassphrase(id, name, permissionRole, hashedPassphrase); + ApiKeyWithPassphrase key = new ApiKeyWithPassphrase(id, name, permissionRole, hashedPassphrase, now()); Lock lock = locks.get(user).writeLock(); lock.lock(); try { diff --git a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyWithPassphrase.java b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyWithPassphrase.java index 75801e47ca..ca56c23693 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyWithPassphrase.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyWithPassphrase.java @@ -28,10 +28,13 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import sonia.scm.xml.XmlInstantAdapter; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -44,4 +47,6 @@ class ApiKeyWithPassphrase { @XmlElement(name = "permission-role") private String permissionRole; private String passphrase; + @XmlJavaTypeAdapter(XmlInstantAdapter.class) + private Instant created; } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java index b72dc82506..953f4638ad 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeResourceTest.java @@ -53,12 +53,12 @@ import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.util.Arrays; import static com.google.inject.util.Providers.of; +import static java.time.Instant.now; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; @@ -229,7 +229,10 @@ public class MeResourceTest { @Test public void shouldGetAllApiKeys() throws URISyntaxException, UnsupportedEncodingException { - when(apiKeyService.getKeys()).thenReturn(Arrays.asList(new ApiKey("1", "key 1", "READ"), new ApiKey("2", "key 2", "WRITE"))); + when(apiKeyService.getKeys()) + .thenReturn(asList( + new ApiKey("1", "key 1", "READ", now()), + new ApiKey("2", "key 2", "WRITE", now()))); MockHttpRequest request = MockHttpRequest.get("/" + MeResource.ME_PATH_V2 + "api_keys"); dispatcher.invoke(request, response); @@ -244,7 +247,10 @@ public class MeResourceTest { @Test public void shouldGetSingleApiKey() throws URISyntaxException, UnsupportedEncodingException { - when(apiKeyService.getKeys()).thenReturn(Arrays.asList(new ApiKey("1", "key 1", "READ"), new ApiKey("2", "key 2", "WRITE"))); + when(apiKeyService.getKeys()) + .thenReturn(asList( + new ApiKey("1", "key 1", "READ", now()), + new ApiKey("2", "key 2", "WRITE", now()))); MockHttpRequest request = MockHttpRequest.get("/" + MeResource.ME_PATH_V2 + "api_keys/1"); dispatcher.invoke(request, response); diff --git a/scm-webapp/src/test/java/sonia/scm/security/ApiKeyTokenHandlerTest.java b/scm-webapp/src/test/java/sonia/scm/security/ApiKeyTokenHandlerTest.java index 9b45323627..198335b286 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/ApiKeyTokenHandlerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/ApiKeyTokenHandlerTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; import java.util.Optional; +import static java.time.Instant.now; import static org.assertj.core.api.Assertions.assertThat; class ApiKeyTokenHandlerTest { @@ -37,7 +38,7 @@ class ApiKeyTokenHandlerTest { @Test void shouldSerializeAndDeserializeToken() { - final String tokenString = handler.createToken("dent", new ApiKey("42", "hg2g", "READ"), "some secret"); + final String tokenString = handler.createToken("dent", new ApiKey("42", "hg2g", "READ", now()), "some secret"); System.out.println(tokenString);