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 3c6790bbb6..90b4165cfd 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 @@ -30,12 +30,16 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import javax.validation.constraints.NotEmpty; + @Getter @Setter @NoArgsConstructor public class ApiKeyDto extends HalRepresentation { + @NotEmpty private String displayName; - private String role; + @NotEmpty + private String permissionRole; public ApiKeyDto(Links links) { super(links); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ApiKeyResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ApiKeyResource.java index 46e831da93..f30382bc3b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ApiKeyResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ApiKeyResource.java @@ -154,7 +154,7 @@ public class ApiKeyResource { schema = @Schema(implementation = ErrorDto.class) )) public Response create(@Valid ApiKeyDto apiKey) { - final ApiKeyService.CreationResult newKey = apiKeyService.createNewKey(apiKey.getDisplayName(), apiKey.getRole()); + final ApiKeyService.CreationResult newKey = apiKeyService.createNewKey(apiKey.getDisplayName(), apiKey.getPermissionRole()); return Response.status(CREATED) .entity(newKey.getToken()) .location(URI.create(resourceLinks.apiKey().self(newKey.getId()))) 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 cb67f1c8b8..9576fb8201 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ApiKey.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ApiKey.java @@ -32,9 +32,9 @@ import lombok.Getter; public class ApiKey { private final String id; private final String displayName; - private final String role; + private final String permissionRole; ApiKey(ApiKeyWithPassphrase apiKeyWithPassphrase) { - this(apiKeyWithPassphrase.getId(), apiKeyWithPassphrase.getDisplayName(), apiKeyWithPassphrase.getRole()); + this(apiKeyWithPassphrase.getId(), apiKeyWithPassphrase.getDisplayName(), apiKeyWithPassphrase.getPermissionRole()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyCollection.java b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyCollection.java index 528620604e..f25f4a7a12 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyCollection.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyCollection.java @@ -31,6 +31,7 @@ import lombok.NoArgsConstructor; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.util.ArrayList; import java.util.Collection; @@ -42,8 +43,9 @@ import static java.util.stream.Collectors.toList; @NoArgsConstructor(access = AccessLevel.PRIVATE) @Getter @XmlAccessorType(XmlAccessType.FIELD) -@XmlRootElement +@XmlRootElement(name = "keys") class ApiKeyCollection { + @XmlElement(name = "key") private Collection keys; public ApiKeyCollection add(ApiKeyWithPassphrase key) { diff --git a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyRealm.java b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyRealm.java index 6ce29e91fb..60fe076974 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyRealm.java @@ -66,9 +66,9 @@ public class ApiKeyRealm extends AuthenticatingRealm { checkArgument(token instanceof BearerToken, "%s is required", BearerToken.class); BearerToken bt = (BearerToken) token; ApiKeyService.CheckResult check = apiKeyService.check(bt.getCredentials()); - RepositoryRole repositoryRole = repositoryRoleManager.get(check.getRole()); + RepositoryRole repositoryRole = repositoryRoleManager.get(check.getPermissionRole()); if (repositoryRole == null) { - throw new AuthorizationException("api key has unknown role: " + check.getRole()); + throw new AuthorizationException("api key has unknown role: " + check.getPermissionRole()); } String scope = "repository:" + String.join(",", repositoryRole.getVerbs()) + ":*"; return helper 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 649ad4eb0f..d666261cae 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyService.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyService.java @@ -72,12 +72,12 @@ public class ApiKeyService { this.passphraseGenerator = passphraseGenerator; } - public CreationResult createNewKey(String name, String role) { + public CreationResult createNewKey(String name, String permissionRole) { String user = currentUser(); String passphrase = passphraseGenerator.get(); String hashedPassphrase = passwordService.encryptPassword(passphrase); final String id = keyGenerator.createKey(); - ApiKeyWithPassphrase key = new ApiKeyWithPassphrase(id, name, role, hashedPassphrase); + ApiKeyWithPassphrase key = new ApiKeyWithPassphrase(id, name, permissionRole, hashedPassphrase); Lock lock = locks.get(user).writeLock(); lock.lock(); try { @@ -132,7 +132,7 @@ public class ApiKeyService { .stream() .filter(key -> key.getId().equals(id)) .filter(key -> passwordService.passwordsMatch(passphrase, key.getPassphrase())) - .map(ApiKeyWithPassphrase::getRole) + .map(ApiKeyWithPassphrase::getPermissionRole) .map(role -> new CheckResult(user, role)) .findAny() .orElseThrow(AuthorizationException::new); @@ -183,6 +183,6 @@ public class ApiKeyService { @AllArgsConstructor public static class CheckResult { private final String user; - private final String role; + private final String permissionRole; } } 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 633a42ad4c..75801e47ca 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ApiKeyWithPassphrase.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ApiKeyWithPassphrase.java @@ -31,6 +31,7 @@ import lombok.NoArgsConstructor; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -38,7 +39,9 @@ import javax.xml.bind.annotation.XmlAccessorType; @XmlAccessorType(XmlAccessType.FIELD) class ApiKeyWithPassphrase { private String id; + @XmlElement(name = "display-name") private String displayName; - private String role; + @XmlElement(name = "permission-role") + private String permissionRole; private String passphrase; } diff --git a/scm-webapp/src/test/java/sonia/scm/security/ApiKeyServiceTest.java b/scm-webapp/src/test/java/sonia/scm/security/ApiKeyServiceTest.java index 21714c53c7..4af23f4686 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/ApiKeyServiceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/ApiKeyServiceTest.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.Test; import sonia.scm.AlreadyExistsException; import sonia.scm.store.DataStore; import sonia.scm.store.DataStoreFactory; +import sonia.scm.store.InMemoryDataStore; import sonia.scm.store.InMemoryDataStoreFactory; import java.util.function.Supplier; @@ -55,7 +56,7 @@ class ApiKeyServiceTest { Supplier passphraseGenerator = () -> Integer.toString(nextKey++); KeyGenerator keyGenerator = () -> Integer.toString(nextId++); ApiKeyTokenHandler tokenHandler = new ApiKeyTokenHandler(); - DataStoreFactory storeFactory = new InMemoryDataStoreFactory(); + DataStoreFactory storeFactory = new InMemoryDataStoreFactory(new InMemoryDataStore()); DataStore store = storeFactory.withType(ApiKeyCollection.class).withName("apiKeys").build(); ApiKeyService service = new ApiKeyService(storeFactory, passwordService, keyGenerator, tokenHandler, passphraseGenerator); @@ -91,12 +92,12 @@ class ApiKeyServiceTest { assertThat(apiKeys.getKeys()).hasSize(1); ApiKeyWithPassphrase key = apiKeys.getKeys().iterator().next(); - assertThat(key.getRole()).isEqualTo("READ"); + assertThat(key.getPermissionRole()).isEqualTo("READ"); assertThat(key.getPassphrase()).isEqualTo("1-hashed"); ApiKeyService.CheckResult role = service.check("dent", "1", "1-hashed"); - assertThat(role).extracting("role").isEqualTo("READ"); + assertThat(role).extracting("permissionRole").isEqualTo("READ"); } @Test @@ -105,7 +106,7 @@ class ApiKeyServiceTest { ApiKeyService.CheckResult role = service.check(newKey); - assertThat(role).extracting("role").isEqualTo("READ"); + assertThat(role).extracting("permissionRole").isEqualTo("READ"); } @Test @@ -129,8 +130,8 @@ class ApiKeyServiceTest { assertThat(apiKeys.getKeys()).hasSize(2); - assertThat(service.check(firstKey.getToken())).extracting("role").isEqualTo("READ"); - assertThat(service.check(secondKey.getToken())).extracting("role").isEqualTo("WRITE"); + assertThat(service.check(firstKey.getToken())).extracting("permissionRole").isEqualTo("READ"); + assertThat(service.check(secondKey.getToken())).extracting("permissionRole").isEqualTo("WRITE"); assertThat(service.getKeys()).extracting("id") .contains(firstKey.getId(), secondKey.getId()); @@ -144,7 +145,7 @@ class ApiKeyServiceTest { service.remove("1"); assertThrows(AuthorizationException.class, () -> service.check(firstKey)); - assertThat(service.check(secondKey)).extracting("role").isEqualTo("WRITE"); + assertThat(service.check(secondKey)).extracting("permissionRole").isEqualTo("WRITE"); } @Test @@ -153,7 +154,7 @@ class ApiKeyServiceTest { assertThrows(AlreadyExistsException.class, () -> service.createNewKey("1", "WRITE")); - assertThat(service.check(firstKey)).extracting("role").isEqualTo("READ"); + assertThat(service.check(firstKey)).extracting("permissionRole").isEqualTo("READ"); } @Test