Create rest endpoint to create new api keys

This commit is contained in:
René Pfeuffer
2020-09-29 14:58:34 +02:00
parent 0dc96c2403
commit 0923c2d63e
7 changed files with 98 additions and 17 deletions

View File

@@ -33,6 +33,7 @@ import javax.inject.Inject;
import java.util.Collection;
import java.util.List;
import static de.otto.edison.hal.Link.link;
import static java.util.stream.Collectors.toList;
public class ApiKeyCollectionToDtoMapper {
@@ -48,8 +49,9 @@ public class ApiKeyCollectionToDtoMapper {
public HalRepresentation map(Collection<ApiKey> keys) {
List<ApiKeyDto> dtos = keys.stream().map(apiKeyDtoMapper::map).collect(toList());
final Links.Builder links = Links.linkingTo();
links.self(resourceLinks.apiKeyCollection().self());
final Links.Builder links = Links.linkingTo()
.self(resourceLinks.apiKeyCollection().self())
.single(link("create", resourceLinks.apiKeyCollection().create()));
return new HalRepresentation(links.build(), Embedded.embedded("apiKeys", dtos));
}
}

View File

@@ -27,10 +27,12 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class ApiKeyDto extends HalRepresentation {
private String displayName;
private String role;

View File

@@ -26,6 +26,7 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -35,11 +36,19 @@ import sonia.scm.security.ApiKeyService;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import static javax.ws.rs.core.Response.Status.CREATED;
import static sonia.scm.NotFoundException.notFound;
public class ApiKeyResource {
@@ -47,12 +56,14 @@ public class ApiKeyResource {
private final ApiKeyService apiKeyService;
private final ApiKeyCollectionToDtoMapper apiKeyCollectionMapper;
private final ApiKeyToApiKeyDtoMapper apiKeyMapper;
private final ResourceLinks resourceLinks;
@Inject
public ApiKeyResource(ApiKeyService apiKeyService, ApiKeyCollectionToDtoMapper apiKeyCollectionMapper, ApiKeyToApiKeyDtoMapper apiKeyMapper) {
public ApiKeyResource(ApiKeyService apiKeyService, ApiKeyCollectionToDtoMapper apiKeyCollectionMapper, ApiKeyToApiKeyDtoMapper apiKeyMapper, ResourceLinks links) {
this.apiKeyService = apiKeyService;
this.apiKeyCollectionMapper = apiKeyCollectionMapper;
this.apiKeyMapper = apiKeyMapper;
this.resourceLinks = links;
}
@GET
@@ -114,4 +125,37 @@ public class ApiKeyResource {
.map(apiKeyMapper::map).findAny()
.orElseThrow(() -> notFound(ContextEntry.ContextBuilder.entity(ApiKey.class, id)));
}
@POST
@Path("")
@Consumes(VndMediaType.API_KEY)
@Operation(summary = "Create new api key for the current user", description = "Creates a new api key for the given user with the role specified in the given key.", tags = "User")
@ApiResponse(
responseCode = "201",
description = "create success",
headers = @Header(
name = "Location",
description = "uri to the created user",
schema = @Schema(type = "string")
),
content = @Content(
mediaType = MediaType.TEXT_PLAIN
)
)
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
@ApiResponse(responseCode = "409", description = "conflict, a key with the given display name already exists")
@ApiResponse(
responseCode = "500",
description = "internal server error",
content = @Content(
mediaType = VndMediaType.ERROR_TYPE,
schema = @Schema(implementation = ErrorDto.class)
))
public Response create(@Valid ApiKeyDto apiKey) {
final ApiKeyService.CreationResult newKey = apiKeyService.createNewKey(apiKey.getDisplayName(), apiKey.getRole());
return Response.status(CREATED)
.entity(newKey.getToken())
.location(URI.create(resourceLinks.apiKey().self(newKey.getId())))
.build();
}
}

View File

@@ -218,6 +218,10 @@ class ResourceLinks {
String self() {
return collectionLinkBuilder.method("apiKeys").parameters().method("getForCurrentUser").parameters().href();
}
String create() {
return collectionLinkBuilder.method("apiKeys").parameters().method("create").parameters().href();
}
}
public ApiKeyLinks apiKey() {

View File

@@ -25,6 +25,8 @@
package sonia.scm.security;
import com.google.common.util.concurrent.Striped;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.util.ThreadContext;
@@ -70,11 +72,12 @@ public class ApiKeyService {
this.passphraseGenerator = passphraseGenerator;
}
public String createNewKey(String name, String role) {
public CreationResult createNewKey(String name, String role) {
String user = currentUser();
String passphrase = passphraseGenerator.get();
String hashedPassphrase = passwordService.encryptPassword(passphrase);
ApiKeyWithPassphrase key = new ApiKeyWithPassphrase(keyGenerator.createKey(), name, role, hashedPassphrase);
final String id = keyGenerator.createKey();
ApiKeyWithPassphrase key = new ApiKeyWithPassphrase(id, name, role, hashedPassphrase);
Lock lock = locks.get(user).writeLock();
lock.lock();
try {
@@ -87,7 +90,8 @@ public class ApiKeyService {
} finally {
lock.unlock();
}
return tokenHandler.createToken(user, new ApiKey(key), passphrase);
final String token = tokenHandler.createToken(user, new ApiKey(key), passphrase);
return new CreationResult(token, id);
}
public void remove(String id) {
@@ -160,4 +164,11 @@ public class ApiKeyService {
.stream()
.anyMatch(key -> key.getDisplayName().equals(name));
}
@Getter
@AllArgsConstructor
public static class CreationResult {
private final String token;
private final String id;
}
}