mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-07-07 11:13:16 +02:00
Feature/profile navigation (#1464)
- Fix bug where profile settings wasn't shown if user cannot change password - Add missing "ApiKey" entry in the single user menu Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
@@ -47,11 +47,11 @@ public class ApiKeyCollectionToDtoMapper {
|
||||
this.resourceLinks = resourceLinks;
|
||||
}
|
||||
|
||||
public HalRepresentation map(Collection<ApiKey> keys) {
|
||||
List<ApiKeyDto> dtos = keys.stream().map(apiKeyDtoMapper::map).collect(toList());
|
||||
public HalRepresentation map(Collection<ApiKey> keys, String user) {
|
||||
List<ApiKeyDto> dtos = keys.stream().map(key -> apiKeyDtoMapper.map(key, user)).collect(toList());
|
||||
final Links.Builder links = Links.linkingTo()
|
||||
.self(resourceLinks.apiKeyCollection().self())
|
||||
.single(link("create", resourceLinks.apiKeyCollection().create()));
|
||||
.self(resourceLinks.apiKeyCollection().self(user))
|
||||
.single(link("create", resourceLinks.apiKeyCollection().create(user)));
|
||||
return new HalRepresentation(links.build(), Embedded.embedded("keys", dtos));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.security.ApiKey;
|
||||
import sonia.scm.security.ApiKeyService;
|
||||
@@ -48,13 +49,17 @@ 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 {
|
||||
/**
|
||||
* Use {@link UserApiKeyResource} instead.
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public class ApiKeyResource {
|
||||
|
||||
private final ApiKeyService apiKeyService;
|
||||
private final ApiKeyCollectionToDtoMapper apiKeyCollectionMapper;
|
||||
@@ -90,7 +95,8 @@ public class ApiKeyResource {
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
public HalRepresentation getForCurrentUser() {
|
||||
return apiKeyCollectionMapper.map(apiKeyService.getKeys());
|
||||
String currentUser = getCurrentUser();
|
||||
return apiKeyCollectionMapper.map(apiKeyService.getKeys(currentUser), currentUser);
|
||||
}
|
||||
|
||||
@GET
|
||||
@@ -121,11 +127,13 @@ public class ApiKeyResource {
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
public ApiKeyDto get(@PathParam("id") String id) {
|
||||
String currentUser = getCurrentUser();
|
||||
|
||||
return apiKeyService
|
||||
.getKeys()
|
||||
.getKeys(currentUser)
|
||||
.stream()
|
||||
.filter(key -> key.getId().equals(id))
|
||||
.map(apiKeyMapper::map).findAny()
|
||||
.map(key -> apiKeyMapper.map(key, currentUser)).findAny()
|
||||
.orElseThrow(() -> notFound(ContextEntry.ContextBuilder.entity(ApiKey.class, id)));
|
||||
}
|
||||
|
||||
@@ -171,10 +179,12 @@ public class ApiKeyResource {
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
public Response create(@Valid ApiKeyDto apiKey) {
|
||||
final ApiKeyService.CreationResult newKey = apiKeyService.createNewKey(apiKey.getDisplayName(), apiKey.getPermissionRole());
|
||||
String currentUser = getCurrentUser();
|
||||
|
||||
final ApiKeyService.CreationResult newKey = apiKeyService.createNewKey(currentUser, apiKey.getDisplayName(), apiKey.getPermissionRole());
|
||||
return Response.status(CREATED)
|
||||
.entity(newKey.getToken())
|
||||
.location(URI.create(resourceLinks.apiKey().self(newKey.getId())))
|
||||
.location(URI.create(resourceLinks.apiKey().self(newKey.getId(), currentUser)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -185,7 +195,10 @@ public class ApiKeyResource {
|
||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||
@ApiResponse(responseCode = "500", description = "internal server error")
|
||||
public void delete(@PathParam("id") String id) {
|
||||
apiKeyService.remove(id);
|
||||
apiKeyService.remove(getCurrentUser(), id);
|
||||
}
|
||||
|
||||
private String getCurrentUser() {
|
||||
return SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,13 @@ public abstract class ApiKeyToApiKeyDtoMapper {
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
abstract ApiKeyDto map(ApiKey key);
|
||||
abstract ApiKeyDto map(ApiKey key, String user);
|
||||
|
||||
@ObjectFactory
|
||||
ApiKeyDto createDto(ApiKey key) {
|
||||
ApiKeyDto createDto(ApiKey key, String user) {
|
||||
Links.Builder links = Links.linkingTo()
|
||||
.self(resourceLinks.apiKey().self(key.getId()))
|
||||
.single(link("delete", resourceLinks.apiKey().delete(key.getId())));
|
||||
.self(resourceLinks.apiKey().self(key.getId(), user))
|
||||
.single(link("delete", resourceLinks.apiKey().delete(key.getId(), user)));
|
||||
return new ApiKeyDto(links.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@ import com.google.common.base.Strings;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import sonia.scm.group.GroupCollector;
|
||||
import sonia.scm.user.EMail;
|
||||
import sonia.scm.user.User;
|
||||
@@ -59,8 +57,7 @@ public class MeDtoFactory extends HalAppenderMapper {
|
||||
}
|
||||
|
||||
public MeDto create() {
|
||||
PrincipalCollection principals = getPrincipalCollection();
|
||||
User user = principals.oneByType(User.class);
|
||||
User user = SecurityUtils.getSubject().getPrincipals().oneByType(User.class);
|
||||
|
||||
MeDto dto = createDto(user);
|
||||
mapUserProperties(user, dto);
|
||||
@@ -79,18 +76,12 @@ public class MeDtoFactory extends HalAppenderMapper {
|
||||
dto.setMail(user.getMail());
|
||||
}
|
||||
|
||||
private PrincipalCollection getPrincipalCollection() {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
return subject.getPrincipals();
|
||||
}
|
||||
|
||||
private void setGeneratedMail(User user, MeDto dto) {
|
||||
if (Strings.isNullOrEmpty(user.getMail())) {
|
||||
dto.setFallbackMail(eMail.getMailOrFallback(user));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private MeDto createDto(User user) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
|
||||
if (UserPermissions.delete(user).isPermitted()) {
|
||||
@@ -106,7 +97,7 @@ public class MeDtoFactory extends HalAppenderMapper {
|
||||
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
||||
}
|
||||
if (UserPermissions.changeApiKeys(user).isPermitted()) {
|
||||
linksBuilder.single(link("apiKeys", resourceLinks.apiKeyCollection().self()));
|
||||
linksBuilder.single(link("apiKeys", resourceLinks.apiKeyCollection().self(user.getName())));
|
||||
}
|
||||
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
|
||||
@@ -64,14 +64,14 @@ public class MeResource {
|
||||
private final UserManager userManager;
|
||||
private final PasswordService passwordService;
|
||||
|
||||
private final Provider<ApiKeyResource> apiKeyResource;
|
||||
private final Provider<ApiKeyResource> apiKeyResourceProvider;
|
||||
|
||||
@Inject
|
||||
public MeResource(MeDtoFactory meDtoFactory, UserManager userManager, PasswordService passwordService, Provider<ApiKeyResource> apiKeyResource) {
|
||||
public MeResource(MeDtoFactory meDtoFactory, UserManager userManager, PasswordService passwordService, Provider<ApiKeyResource> apiKeyResourceProvider) {
|
||||
this.meDtoFactory = meDtoFactory;
|
||||
this.userManager = userManager;
|
||||
this.passwordService = passwordService;
|
||||
this.apiKeyResource = apiKeyResource;
|
||||
this.apiKeyResourceProvider = apiKeyResourceProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,6 +142,6 @@ public class MeResource {
|
||||
|
||||
@Path("api_keys")
|
||||
public ApiKeyResource apiKeys() {
|
||||
return apiKeyResource.get();
|
||||
return apiKeyResourceProvider.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,10 +101,12 @@ class ResourceLinks {
|
||||
static class UserLinks {
|
||||
private final LinkBuilder userLinkBuilder;
|
||||
private final LinkBuilder publicKeyLinkBuilder;
|
||||
private final LinkBuilder apiKeyLinkBuilder;
|
||||
|
||||
UserLinks(ScmPathInfo pathInfo) {
|
||||
userLinkBuilder = new LinkBuilder(pathInfo, UserRootResource.class, UserResource.class);
|
||||
publicKeyLinkBuilder = new LinkBuilder(pathInfo, UserPublicKeyResource.class);
|
||||
apiKeyLinkBuilder = new LinkBuilder(pathInfo, UserApiKeyResource.class);
|
||||
}
|
||||
|
||||
String self(String name) {
|
||||
@@ -134,6 +136,10 @@ class ResourceLinks {
|
||||
public String publicKeys(String name) {
|
||||
return publicKeyLinkBuilder.method("findAll").parameters(name).href();
|
||||
}
|
||||
|
||||
public String apiKeys(String name) {
|
||||
return apiKeyLinkBuilder.method("findAll").parameters(name).href();
|
||||
}
|
||||
}
|
||||
|
||||
interface WithPermissionLinks {
|
||||
@@ -220,15 +226,15 @@ class ResourceLinks {
|
||||
private final LinkBuilder collectionLinkBuilder;
|
||||
|
||||
ApiKeyCollectionLinks(ScmPathInfo pathInfo) {
|
||||
this.collectionLinkBuilder = new LinkBuilder(pathInfo, MeResource.class, ApiKeyResource.class);
|
||||
this.collectionLinkBuilder = new LinkBuilder(pathInfo, UserRootResource.class, UserApiKeyResource.class);
|
||||
}
|
||||
|
||||
String self() {
|
||||
return collectionLinkBuilder.method("apiKeys").parameters().method("getForCurrentUser").parameters().href();
|
||||
String self(String username) {
|
||||
return collectionLinkBuilder.method("apiKeys").parameters().method("findAll").parameters(username).href();
|
||||
}
|
||||
|
||||
String create() {
|
||||
return collectionLinkBuilder.method("apiKeys").parameters().method("create").parameters().href();
|
||||
String create(String username) {
|
||||
return collectionLinkBuilder.method("apiKeys").parameters().method("create").parameters(username).href();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,15 +246,15 @@ class ResourceLinks {
|
||||
private final LinkBuilder apiKeyLinkBuilder;
|
||||
|
||||
ApiKeyLinks(ScmPathInfo pathInfo) {
|
||||
this.apiKeyLinkBuilder = new LinkBuilder(pathInfo, MeResource.class, ApiKeyResource.class);
|
||||
this.apiKeyLinkBuilder = new LinkBuilder(pathInfo, UserRootResource.class, UserApiKeyResource.class);
|
||||
}
|
||||
|
||||
String self(String id) {
|
||||
return apiKeyLinkBuilder.method("apiKeys").parameters().method("get").parameters(id).href();
|
||||
String self(String id, String name) {
|
||||
return apiKeyLinkBuilder.method("apiKeys").parameters(name).method("get").parameters(id).href();
|
||||
}
|
||||
|
||||
String delete(String id) {
|
||||
return apiKeyLinkBuilder.method("apiKeys").parameters().method("delete").parameters(id).href();
|
||||
String delete(String id, String name) {
|
||||
return apiKeyLinkBuilder.method("apiKeys").parameters(name).method("delete").parameters(id).href();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
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.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.security.ApiKey;
|
||||
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.DELETE;
|
||||
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;
|
||||
|
||||
@Path("v2/users/{id}/api_keys")
|
||||
public class UserApiKeyResource {
|
||||
|
||||
private final ApiKeyService apiKeyService;
|
||||
private final ApiKeyCollectionToDtoMapper apiKeyCollectionMapper;
|
||||
private final ApiKeyToApiKeyDtoMapper apiKeyMapper;
|
||||
private final ResourceLinks resourceLinks;
|
||||
|
||||
@Inject
|
||||
public UserApiKeyResource(ApiKeyService apiKeyService, ApiKeyCollectionToDtoMapper apiKeyCollectionMapper, ApiKeyToApiKeyDtoMapper apiKeyMapper, ResourceLinks links) {
|
||||
this.apiKeyService = apiKeyService;
|
||||
this.apiKeyCollectionMapper = apiKeyCollectionMapper;
|
||||
this.apiKeyMapper = apiKeyMapper;
|
||||
this.resourceLinks = links;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("")
|
||||
@Produces(VndMediaType.API_KEY_COLLECTION)
|
||||
@Operation(summary = "Get all api keys for user", description = "Returns all registered api keys for the given username.", tags = "User", operationId = "get_all_api_keys")
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "success",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.API_KEY_COLLECTION,
|
||||
schema = @Schema(implementation = HalRepresentation.class)
|
||||
)
|
||||
)
|
||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||
@ApiResponse(
|
||||
responseCode = "500",
|
||||
description = "internal server error",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
public HalRepresentation findAll(@PathParam("id") String id) {
|
||||
return apiKeyCollectionMapper.map(apiKeyService.getKeys(id), id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{keyId}")
|
||||
@Produces(VndMediaType.API_KEY)
|
||||
@Operation(summary = "Get single api key for user", description = "Returns a single registered api key with the given id for user.", tags = "User", operationId = "get_single_api_key")
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "success",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.API_KEY,
|
||||
schema = @Schema(implementation = HalRepresentation.class)
|
||||
)
|
||||
)
|
||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||
@ApiResponse(
|
||||
responseCode = "404",
|
||||
description = "not found / key for given id not available",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
@ApiResponse(
|
||||
responseCode = "500",
|
||||
description = "internal server error",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
public ApiKeyDto get(@PathParam("id") String id, @PathParam("keyId") String keyId) {
|
||||
return apiKeyService
|
||||
.getKeys(id)
|
||||
.stream()
|
||||
.filter(key -> key.getId().equals(keyId))
|
||||
.map(key -> apiKeyMapper.map(key, id))
|
||||
.findAny()
|
||||
.orElseThrow(() -> notFound(ContextEntry.ContextBuilder.entity(ApiKey.class, keyId)));
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("")
|
||||
@Consumes(VndMediaType.API_KEY)
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Operation(
|
||||
summary = "Create new api key for user",
|
||||
description = "Creates a new api key for the given user with the role specified in the given key.",
|
||||
tags = "User",
|
||||
operationId = "create_api_key",
|
||||
requestBody = @RequestBody(
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.API_KEY,
|
||||
schema = @Schema(implementation = CreateApiKeyDto.class),
|
||||
examples = @ExampleObject(
|
||||
name = "Create a new api key named readKey with READ permission role.",
|
||||
value = "{\n \"displayName\":\"readKey\",\n \"permissionRole\":\"READ\"\n}",
|
||||
summary = "Create new api key"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
@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, @PathParam("id") String id) {
|
||||
final ApiKeyService.CreationResult newKey = apiKeyService.createNewKey(id, apiKey.getDisplayName(), apiKey.getPermissionRole());
|
||||
return Response.status(CREATED)
|
||||
.entity(newKey.getToken())
|
||||
.location(URI.create(resourceLinks.apiKey().self(newKey.getId(), id)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{keyId}")
|
||||
@Operation(summary = "Delete api key", description = "Deletes the api key with the given id for user.", tags = "User", operationId = "delete_api_key")
|
||||
@ApiResponse(responseCode = "204", description = "delete success or nothing to delete")
|
||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||
@ApiResponse(responseCode = "500", description = "internal server error")
|
||||
public void delete( @PathParam("id") String id, @PathParam("keyId") String keyId) {
|
||||
apiKeyService.remove(id, keyId);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||
@@ -32,7 +32,7 @@ import javax.inject.Provider;
|
||||
import javax.ws.rs.Path;
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to manage users.
|
||||
* RESTful Web Service Resource to manage users.
|
||||
*/
|
||||
@OpenAPIDefinition(tags = {
|
||||
@Tag(name = "User", description = "User related endpoints")
|
||||
@@ -44,12 +44,14 @@ public class UserRootResource {
|
||||
|
||||
private final Provider<UserCollectionResource> userCollectionResource;
|
||||
private final Provider<UserResource> userResource;
|
||||
private final Provider<UserApiKeyResource> userApiKeyResource;
|
||||
|
||||
@Inject
|
||||
public UserRootResource(Provider<UserCollectionResource> userCollectionResource,
|
||||
Provider<UserResource> userResource) {
|
||||
Provider<UserResource> userResource, Provider<UserApiKeyResource> userApiKeyResource) {
|
||||
this.userCollectionResource = userCollectionResource;
|
||||
this.userResource = userResource;
|
||||
this.userApiKeyResource = userApiKeyResource;
|
||||
}
|
||||
|
||||
@Path("")
|
||||
@@ -61,4 +63,9 @@ public class UserRootResource {
|
||||
public UserResource getUserResource() {
|
||||
return userResource.get();
|
||||
}
|
||||
|
||||
@Path("{id}/api_keys")
|
||||
public UserApiKeyResource apiKeys() {
|
||||
return userApiKeyResource.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
||||
if (UserPermissions.modify(user).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.user().update(user.getName())));
|
||||
linksBuilder.single(link("publicKeys", resourceLinks.user().publicKeys(user.getName())));
|
||||
linksBuilder.single(link("apiKeys", resourceLinks.user().apiKeys(user.getName())));
|
||||
if (user.isExternal()) {
|
||||
linksBuilder.single(link("convertToInternal", resourceLinks.user().toInternal(user.getName())));
|
||||
} else {
|
||||
|
||||
@@ -30,7 +30,6 @@ 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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ContextEntry;
|
||||
@@ -80,19 +79,18 @@ public class ApiKeyService {
|
||||
this.passphraseGenerator = passphraseGenerator;
|
||||
}
|
||||
|
||||
public CreationResult createNewKey(String name, String permissionRole) {
|
||||
String user = currentUser();
|
||||
UserPermissions.changeApiKeys(user).check();
|
||||
public CreationResult createNewKey(String username, String keyDisplayName, String permissionRole) {
|
||||
UserPermissions.changeApiKeys(username).check();
|
||||
String passphrase = passphraseGenerator.get();
|
||||
String hashedPassphrase = passwordService.encryptPassword(passphrase);
|
||||
String id = keyGenerator.createKey();
|
||||
ApiKeyWithPassphrase key = new ApiKeyWithPassphrase(id, name, permissionRole, hashedPassphrase, now());
|
||||
doSynchronized(user, true, () -> {
|
||||
persistKey(name, user, key);
|
||||
ApiKeyWithPassphrase key = new ApiKeyWithPassphrase(id, keyDisplayName, permissionRole, hashedPassphrase, now());
|
||||
doSynchronized(username, true, () -> {
|
||||
persistKey(keyDisplayName, username, key);
|
||||
return null;
|
||||
});
|
||||
String token = tokenHandler.createToken(user, new ApiKey(key), passphrase);
|
||||
LOG.info("created new api key for user {} with role {}", user, permissionRole);
|
||||
String token = tokenHandler.createToken(username, new ApiKey(key), passphrase);
|
||||
LOG.info("created new api key for user {} with role {}", username, permissionRole);
|
||||
return new CreationResult(token, id);
|
||||
}
|
||||
|
||||
@@ -105,18 +103,17 @@ public class ApiKeyService {
|
||||
store.put(user, newApiKeyCollection);
|
||||
}
|
||||
|
||||
public void remove(String id) {
|
||||
String user = currentUser();
|
||||
UserPermissions.changeApiKeys(user).check();
|
||||
doSynchronized(user, true, () -> {
|
||||
if (!containsId(user, id)) {
|
||||
public void remove(String username, String id) {
|
||||
UserPermissions.changeApiKeys(username).check();
|
||||
doSynchronized(username, true, () -> {
|
||||
if (!containsId(username, id)) {
|
||||
return null;
|
||||
}
|
||||
store.getOptional(user).ifPresent(
|
||||
store.getOptional(username).ifPresent(
|
||||
apiKeyCollection -> {
|
||||
ApiKeyCollection newApiKeyCollection = apiKeyCollection.remove(key -> id.equals(key.getId()));
|
||||
store.put(user, newApiKeyCollection);
|
||||
LOG.info("removed api key for user {}", user);
|
||||
store.put(username, newApiKeyCollection);
|
||||
LOG.info("removed api key for user {}", username);
|
||||
}
|
||||
);
|
||||
return null;
|
||||
@@ -154,8 +151,8 @@ public class ApiKeyService {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Collection<ApiKey> getKeys() {
|
||||
return store.getOptional(currentUser())
|
||||
public Collection<ApiKey> getKeys(String user) {
|
||||
return store.getOptional(user)
|
||||
.map(ApiKeyCollection::getKeys)
|
||||
.map(Collection::stream)
|
||||
.orElse(Stream.empty())
|
||||
@@ -163,10 +160,6 @@ public class ApiKeyService {
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private String currentUser() {
|
||||
return ThreadContext.getSubject().getPrincipals().getPrimaryPrincipal().toString();
|
||||
}
|
||||
|
||||
private boolean containsId(String user, String id) {
|
||||
return store
|
||||
.getOptional(user)
|
||||
|
||||
Reference in New Issue
Block a user