From 76b9100e7e687444f631d4af05881f716c625aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 3 May 2019 17:44:27 +0200 Subject: [PATCH] Add endpoint for repository roles (tests pending) --- .../AbstractRepositoryRoleManager.java | 78 +++++++ .../sonia/scm/repository/RepositoryRole.java | 6 +- .../scm/repository/RepositoryRoleEvent.java | 69 +++++++ .../scm/repository/RepositoryRoleManager.java | 44 ++++ .../RepositoryRoleModificationEvent.java | 67 ++++++ .../main/java/sonia/scm/web/VndMediaType.java | 3 + .../main/java/sonia/scm/ScmServletModule.java | 3 + .../scm/api/v2/resources/MapperModule.java | 4 + .../RepositoryRoleCollectionResource.java | 95 +++++++++ .../RepositoryRoleCollectionToDtoMapper.java | 34 ++++ .../api/v2/resources/RepositoryRoleDto.java | 22 ++ ...positoryRoleDtoToRepositoryRoleMapper.java | 14 ++ .../v2/resources/RepositoryRoleResource.java | 103 ++++++++++ .../resources/RepositoryRoleRootResource.java | 34 ++++ ...positoryRoleToRepositoryRoleDtoMapper.java | 42 ++++ .../scm/api/v2/resources/ResourceLinks.java | 43 +++- .../DefaultRepositoryRoleManager.java | 191 ++++++++++++++++++ 17 files changed, 849 insertions(+), 3 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryRoleManager.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/RepositoryRoleEvent.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/RepositoryRoleManager.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/RepositoryRoleModificationEvent.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionToDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDto.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDtoToRepositoryRoleMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleRootResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleToRepositoryRoleDtoMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryRoleManager.java diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryRoleManager.java b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryRoleManager.java new file mode 100644 index 0000000000..1db0065e8b --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryRoleManager.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository; + +import sonia.scm.HandlerEventType; +import sonia.scm.event.ScmEventBus; + +/** + * Abstract base class for {@link RepositoryRoleManager} implementations. This class + * implements the listener methods of the {@link RepositoryRoleManager} interface. + */ +public abstract class AbstractRepositoryRoleManager implements RepositoryRoleManager { + + /** + * Send a {@link RepositoryRoleEvent} to the {@link ScmEventBus}. + * + * @param event type of change event + * @param repositoryRole repositoryRole that has changed + * @param oldRepositoryRole old repositoryRole + */ + protected void fireEvent(HandlerEventType event, RepositoryRole repositoryRole, RepositoryRole oldRepositoryRole) + { + fireEvent(new RepositoryRoleModificationEvent(event, repositoryRole, oldRepositoryRole)); + } + + /** + * Creates a new {@link RepositoryRoleEvent} and calls {@link #fireEvent(RepositoryRoleEvent)}. + * + * @param repositoryRole repositoryRole that has changed + * @param event type of change event + */ + protected void fireEvent(HandlerEventType event, RepositoryRole repositoryRole) + { + fireEvent(new RepositoryRoleEvent(event, repositoryRole)); + } + + /** + * Send a {@link RepositoryRoleEvent} to the {@link ScmEventBus}. + * + * @param event repositoryRole event + * @since 1.48 + */ + protected void fireEvent(RepositoryRoleEvent event) + { + ScmEventBus.getInstance().post(event); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRole.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRole.java index ca77f255f8..42ee8ffa65 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryRole.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRole.java @@ -33,11 +33,12 @@ package sonia.scm.repository; +import com.github.sdorra.ssp.PermissionObject; +import com.github.sdorra.ssp.StaticPermissions; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.base.Strings; import sonia.scm.ModelObject; -import sonia.scm.user.User; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -56,9 +57,10 @@ import static java.util.Collections.unmodifiableSet; * Custom role with specific permissions related to {@link Repository}. * This object should be immutable, but could not be due to mapstruct. */ +@StaticPermissions(value = "repositoryRole", permissions = {}, globalPermissions = {"read", "modify"}) @XmlRootElement(name = "roles") @XmlAccessorType(XmlAccessType.FIELD) -public class RepositoryRole implements ModelObject { +public class RepositoryRole implements ModelObject, PermissionObject { private static final long serialVersionUID = -723588336073192740L; diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleEvent.java new file mode 100644 index 0000000000..fcd21bfbcd --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleEvent.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository; + +import sonia.scm.HandlerEventType; +import sonia.scm.event.AbstractHandlerEvent; +import sonia.scm.event.Event; + +/** + * The RepositoryRoleEvent is fired if a repository role object changes. + * @since 2.0 + */ +@Event +public class RepositoryRoleEvent extends AbstractHandlerEvent { + + /** + * Constructs a new repositoryRole event. + * + * + * @param eventType event type + * @param repositoryRole changed repositoryRole + */ + public RepositoryRoleEvent(HandlerEventType eventType, RepositoryRole repositoryRole) { + super(eventType, repositoryRole); + } + + /** + * Constructs a new repositoryRole event. + * + * + * @param eventType type of the event + * @param repositoryRole changed repositoryRole + * @param oldRepositoryRole old repositoryRole + */ + public RepositoryRoleEvent(HandlerEventType eventType, RepositoryRole repositoryRole, RepositoryRole oldRepositoryRole) { + super(eventType, repositoryRole, oldRepositoryRole); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleManager.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleManager.java new file mode 100644 index 0000000000..c7e1971110 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleManager.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository; + +import sonia.scm.Manager; +import sonia.scm.search.Searchable; + +/** + * The central class for managing {@link RepositoryRole} objects. + * This class is a singleton and is available via injection. + */ +public interface RepositoryRoleManager extends Manager { +} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleModificationEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleModificationEvent.java new file mode 100644 index 0000000000..eabeb26b2e --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryRoleModificationEvent.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.repository; + +import sonia.scm.HandlerEventType; +import sonia.scm.ModificationHandlerEvent; +import sonia.scm.event.Event; + +/** + * Event which is fired whenever a repository role is modified. + * + * @since 2.0 + */ +@Event +public class RepositoryRoleModificationEvent extends RepositoryRoleEvent implements ModificationHandlerEvent +{ + + private final RepositoryRole itemBeforeModification; + + /** + * Constructs a new {@link RepositoryRoleModificationEvent}. + * + * @param eventType type of event + * @param item changed repository role + * @param itemBeforeModification changed repository role before it was modified + */ + public RepositoryRoleModificationEvent(HandlerEventType eventType, RepositoryRole item, RepositoryRole itemBeforeModification) + { + super(eventType, item); + this.itemBeforeModification = itemBeforeModification; + } + + @Override + public RepositoryRole getItemBeforeModification() + { + return itemBeforeModification; + } + +} diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 4dcb2f1d96..cad62821ed 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -53,6 +53,9 @@ public class VndMediaType { public static final String SOURCE = PREFIX + "source" + SUFFIX; public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX; + public static final String REPOSITORY_ROLE = PREFIX + "repositoryRole" + SUFFIX; + public static final String REPOSITORY_ROLE_COLLECTION = PREFIX + "repositoryRoleCollection" + SUFFIX; + private VndMediaType() { } diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 898baf33e1..8cc34b6f23 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -67,6 +67,7 @@ import sonia.scm.plugin.PluginLoader; import sonia.scm.plugin.PluginManager; import sonia.scm.repository.DefaultRepositoryManager; import sonia.scm.repository.DefaultRepositoryProvider; +import sonia.scm.repository.DefaultRepositoryRoleManager; import sonia.scm.repository.HealthCheckContextListener; import sonia.scm.repository.NamespaceStrategy; import sonia.scm.repository.NamespaceStrategyProvider; @@ -76,6 +77,7 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryManagerProvider; import sonia.scm.repository.RepositoryProvider; import sonia.scm.repository.RepositoryRoleDAO; +import sonia.scm.repository.RepositoryRoleManager; import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.repository.spi.HookEventFacade; @@ -270,6 +272,7 @@ public class ScmServletModule extends ServletModule bind(UserDAO.class, XmlUserDAO.class); bind(RepositoryDAO.class, XmlRepositoryDAO.class); bind(RepositoryRoleDAO.class, XmlRepositoryRoleDAO.class); + bind(RepositoryRoleManager.class).to(DefaultRepositoryRoleManager.class); bindDecorated(RepositoryManager.class, DefaultRepositoryManager.class, RepositoryManagerProvider.class); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index c74f16ad70..cf09eeb128 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -28,6 +28,10 @@ public class MapperModule extends AbstractModule { bind(RepositoryPermissionDtoToRepositoryPermissionMapper.class).to(Mappers.getMapper(RepositoryPermissionDtoToRepositoryPermissionMapper.class).getClass()); bind(RepositoryPermissionToRepositoryPermissionDtoMapper.class).to(Mappers.getMapper(RepositoryPermissionToRepositoryPermissionDtoMapper.class).getClass()); + bind(RepositoryRoleToRepositoryRoleDtoMapper.class).to(Mappers.getMapper(RepositoryRoleToRepositoryRoleDtoMapper.class).getClass()); + bind(RepositoryRoleDtoToRepositoryRoleMapper.class).to(Mappers.getMapper(RepositoryRoleDtoToRepositoryRoleMapper.class).getClass()); + bind(RepositoryRoleCollectionToDtoMapper.class); + bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(DefaultChangesetToChangesetDtoMapper.class).getClass()); bind(ChangesetToParentDtoMapper.class).to(Mappers.getMapper(ChangesetToParentDtoMapper.class).getClass()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java new file mode 100644 index 0000000000..12f52156f0 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionResource.java @@ -0,0 +1,95 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.ResponseHeader; +import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import org.apache.shiro.authc.credential.PasswordService; +import sonia.scm.repository.RepositoryRole; +import sonia.scm.repository.RepositoryRoleManager; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.validation.Valid; +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +public class RepositoryRoleCollectionResource { + + private static final int DEFAULT_PAGE_SIZE = 10; + private final RepositoryRoleDtoToRepositoryRoleMapper dtoToRepositoryRoleMapper; + private final RepositoryRoleCollectionToDtoMapper repositoryRoleCollectionToDtoMapper; + private final ResourceLinks resourceLinks; + + private final IdResourceManagerAdapter adapter; + + @Inject + public RepositoryRoleCollectionResource(RepositoryRoleManager manager, RepositoryRoleDtoToRepositoryRoleMapper dtoToRepositoryRoleMapper, + RepositoryRoleCollectionToDtoMapper repositoryRoleCollectionToDtoMapper, ResourceLinks resourceLinks) { + this.dtoToRepositoryRoleMapper = dtoToRepositoryRoleMapper; + this.repositoryRoleCollectionToDtoMapper = repositoryRoleCollectionToDtoMapper; + this.adapter = new IdResourceManagerAdapter<>(manager, RepositoryRole.class); + this.resourceLinks = resourceLinks; + } + + /** + * Returns all repository roles for a given page number with a given page size (default page size is {@value DEFAULT_PAGE_SIZE}). + * + * Note: This method requires "repositoryRole" privilege. + * + * @param page the number of the requested page + * @param pageSize the page size (default page size is {@value DEFAULT_PAGE_SIZE}) + * @param sortBy sort parameter (if empty - undefined sorting) + * @param desc sort direction desc or asc + */ + @GET + @Path("") + @Produces(VndMediaType.REPOSITORY_ROLE_COLLECTION) + @TypeHint(CollectionDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "\"sortBy\" field unknown"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current repositoryRole does not have the \"repositoryRole\" privilege"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response getAll(@DefaultValue("0") @QueryParam("page") int page, + @DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize, + @QueryParam("sortBy") String sortBy, + @DefaultValue("false") @QueryParam("desc") boolean desc + ) { + return adapter.getAll(page, pageSize, x -> true, sortBy, desc, + pageResult -> repositoryRoleCollectionToDtoMapper.map(page, pageSize, pageResult)); + } + + /** + * Creates a new repository role. + * + * Note: This method requires "repositoryRole" privilege. + * + * @param repositoryRole The repositoryRole to be created. + * @return A response with the link to the new repository role (if created successfully). + */ + @POST + @Path("") + @Consumes(VndMediaType.REPOSITORY_ROLE) + @StatusCodes({ + @ResponseCode(code = 201, condition = "create success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repositoryRole\" privilege"), + @ResponseCode(code = 409, condition = "conflict, a repository role with this name already exists"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + @ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repositoryRole")) + public Response create(@Valid RepositoryRoleDto repositoryRole) { + return adapter.create(repositoryRole, () -> dtoToRepositoryRoleMapper.map(repositoryRole), u -> resourceLinks.repositoryRole().self(u.getName())); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionToDtoMapper.java new file mode 100644 index 0000000000..ab34efd7fe --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleCollectionToDtoMapper.java @@ -0,0 +1,34 @@ +package sonia.scm.api.v2.resources; + +import sonia.scm.PageResult; +import sonia.scm.repository.RepositoryRole; +import sonia.scm.repository.RepositoryRolePermissions; + +import javax.inject.Inject; +import java.util.Optional; + +import static java.util.Optional.empty; +import static java.util.Optional.of; + +public class RepositoryRoleCollectionToDtoMapper extends BasicCollectionToDtoMapper { + + private final ResourceLinks resourceLinks; + + @Inject + public RepositoryRoleCollectionToDtoMapper(RepositoryRoleToRepositoryRoleDtoMapper repositoryRoleToDtoMapper, ResourceLinks resourceLinks) { + super("repositoryRoles", repositoryRoleToDtoMapper); + this.resourceLinks = resourceLinks; + } + + public CollectionDto map(int pageNumber, int pageSize, PageResult pageResult) { + return map(pageNumber, pageSize, pageResult, this.createSelfLink(), this.createCreateLink()); + } + + Optional createCreateLink() { + return RepositoryRolePermissions.modify().isPermitted() ? of(resourceLinks.repositoryRoleCollection().create()): empty(); + } + + String createSelfLink() { + return resourceLinks.repositoryRoleCollection().self(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDto.java new file mode 100644 index 0000000000..9bd64b4ce8 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDto.java @@ -0,0 +1,22 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Collection; + +@Getter +@Setter +@NoArgsConstructor +public class RepositoryRoleDto extends HalRepresentation { + private String name; + private Collection verbs; + + RepositoryRoleDto(Links links, Embedded embedded) { + super(links, embedded); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDtoToRepositoryRoleMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDtoToRepositoryRoleMapper.java new file mode 100644 index 0000000000..dc969b59b3 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDtoToRepositoryRoleMapper.java @@ -0,0 +1,14 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import sonia.scm.repository.RepositoryRole; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class RepositoryRoleDtoToRepositoryRoleMapper extends BaseDtoMapper { + + @Mapping(target = "creationDate", ignore = true) + public abstract RepositoryRole map(RepositoryRoleDto repositoryRoleDto); +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleResource.java new file mode 100644 index 0000000000..59adbce264 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleResource.java @@ -0,0 +1,103 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import sonia.scm.repository.RepositoryRole; +import sonia.scm.repository.RepositoryRoleManager; +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.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +public class RepositoryRoleResource { + + private final RepositoryRoleDtoToRepositoryRoleMapper dtoToRepositoryRoleMapper; + private final RepositoryRoleToRepositoryRoleDtoMapper repositoryRoleToDtoMapper; + + private final IdResourceManagerAdapter adapter; + + @Inject + public RepositoryRoleResource( + RepositoryRoleDtoToRepositoryRoleMapper dtoToRepositoryRoleMapper, + RepositoryRoleToRepositoryRoleDtoMapper repositoryRoleToDtoMapper, + RepositoryRoleManager manager) { + this.dtoToRepositoryRoleMapper = dtoToRepositoryRoleMapper; + this.repositoryRoleToDtoMapper = repositoryRoleToDtoMapper; + this.adapter = new IdResourceManagerAdapter<>(manager, RepositoryRole.class); + } + + /** + * Returns a repository role. + * + * Note: This method requires "repositoryRole" privilege. + * + * @param name the id/name of the repository role + */ + @GET + @Path("") + @Produces(VndMediaType.REPOSITORY_ROLE) + @TypeHint(RepositoryRoleDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the repository role"), + @ResponseCode(code = 404, condition = "not found, no repository role with the specified name available"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get(@PathParam("name") String name) { + return adapter.get(name, repositoryRoleToDtoMapper::map); + } + + /** + * Deletes a repository role. + * + * Note: This method requires "repositoryRole" privilege. + * + * @param name the name of the repository role to delete. + */ + @DELETE + @Path("") + @StatusCodes({ + @ResponseCode(code = 204, condition = "delete success or nothing to delete"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repositoryRole\" privilege"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response delete(@PathParam("name") String name) { + return adapter.delete(name); + } + + /** + * Modifies the given repository role. + * + * Note: This method requires "repositoryRole" privilege. + * + * @param name name of the repository role to be modified + * @param repositoryRole repository role object to modify + */ + @PUT + @Path("") + @Consumes(VndMediaType.REPOSITORY_ROLE) + @StatusCodes({ + @ResponseCode(code = 204, condition = "update success"), + @ResponseCode(code = 400, condition = "Invalid body, e.g. illegal change of repository role name"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repositoryRole\" privilege"), + @ResponseCode(code = 404, condition = "not found, no repository role with the specified name available"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response update(@PathParam("name") String name, @Valid RepositoryRoleDto repositoryRole) { + return adapter.update(name, existing -> dtoToRepositoryRoleMapper.map(repositoryRole)); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleRootResource.java new file mode 100644 index 0000000000..f4adb02438 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleRootResource.java @@ -0,0 +1,34 @@ +package sonia.scm.api.v2.resources; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.ws.rs.Path; + +/** + * RESTful web service resource to manage repository roles. + */ +@Path(RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2) +public class RepositoryRoleRootResource { + + static final String REPOSITORY_ROLES_PATH_V2 = "v2/repository-roles/"; + + private final Provider repositoryRoleCollectionResource; + private final Provider repositoryRoleResource; + + @Inject + public RepositoryRoleRootResource(Provider repositoryRoleCollectionResource, + Provider repositoryRoleResource) { + this.repositoryRoleCollectionResource = repositoryRoleCollectionResource; + this.repositoryRoleResource = repositoryRoleResource; + } + + @Path("") + public RepositoryRoleCollectionResource getRepositoryRoleCollectionResource() { + return repositoryRoleCollectionResource.get(); + } + + @Path("{name}") + public RepositoryRoleResource getRepositoryRoleResource() { + return repositoryRoleResource.get(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleToRepositoryRoleDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleToRepositoryRoleDtoMapper.java new file mode 100644 index 0000000000..86b4709fd2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleToRepositoryRoleDtoMapper.java @@ -0,0 +1,42 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Embedded; +import de.otto.edison.hal.Links; +import org.mapstruct.Mapper; +import org.mapstruct.ObjectFactory; +import sonia.scm.repository.RepositoryRole; +import sonia.scm.repository.RepositoryRoleManager; +import sonia.scm.repository.RepositoryRolePermissions; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class RepositoryRoleToRepositoryRoleDtoMapper extends BaseMapper { + + @Inject + private RepositoryRoleManager repositoryRoleManager; + + @Inject + private ResourceLinks resourceLinks; + + @ObjectFactory + RepositoryRoleDto createDto(RepositoryRole repositoryRole) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.repositoryRole().self(repositoryRole.getName())); + if (RepositoryRolePermissions.modify().isPermitted()) { + linksBuilder.single(link("delete", resourceLinks.repositoryRole().delete(repositoryRole.getName()))); + linksBuilder.single(link("update", resourceLinks.repositoryRole().update(repositoryRole.getName()))); + } + + Embedded.Builder embeddedBuilder = embeddedBuilder(); + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), repositoryRole); + + return new RepositoryRoleDto(linksBuilder.build(), embeddedBuilder.build()); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index ff1013bb76..141645a09c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -172,7 +172,6 @@ class ResourceLinks { } } - UserCollectionLinks userCollection() { return new UserCollectionLinks(scmPathInfoStore.get()); } @@ -522,8 +521,50 @@ class ResourceLinks { public String content(String namespace, String name, String revision, String path) { return addPath(sourceLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("content").parameters().method("get").parameters(revision, "").href(), path); } + } + RepositoryRoleLinks repositoryRole() { + return new RepositoryRoleLinks(scmPathInfoStore.get()); + } + static class RepositoryRoleLinks { + private final LinkBuilder repositoryRoleLinkBuilder; + + RepositoryRoleLinks(ScmPathInfo pathInfo) { + repositoryRoleLinkBuilder = new LinkBuilder(pathInfo, RepositoryRoleRootResource.class, RepositoryRoleResource.class); + } + + String self(String name) { + return repositoryRoleLinkBuilder.method("getRepositoryRoleResource").parameters(name).method("get").parameters().href(); + } + + String delete(String name) { + return repositoryRoleLinkBuilder.method("getRepositoryRoleResource").parameters(name).method("delete").parameters().href(); + } + + String update(String name) { + return repositoryRoleLinkBuilder.method("getRepositoryRoleResource").parameters(name).method("update").parameters().href(); + } + } + + RepositoryRoleCollectionLinks repositoryRoleCollection() { + return new RepositoryRoleCollectionLinks(scmPathInfoStore.get()); + } + + static class RepositoryRoleCollectionLinks { + private final LinkBuilder collectionLinkBuilder; + + RepositoryRoleCollectionLinks(ScmPathInfo pathInfo) { + collectionLinkBuilder = new LinkBuilder(pathInfo, RepositoryRoleRootResource.class, RepositoryRoleCollectionResource.class); + } + + String self() { + return collectionLinkBuilder.method("getRepositoryRoleCollectionResource").parameters().method("getAll").parameters().href(); + } + + String create() { + return collectionLinkBuilder.method("getRepositoryRoleCollectionResource").parameters().method("create").parameters().href(); + } } public RepositoryPermissionLinks repositoryPermission() { diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryRoleManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryRoleManager.java new file mode 100644 index 0000000000..bc52d38dd2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryRoleManager.java @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository; + +import com.github.sdorra.ssp.PermissionActionCheck; +import com.github.sdorra.ssp.PermissionCheck; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.EagerSingleton; +import sonia.scm.HandlerEventType; +import sonia.scm.ManagerDaoAdapter; +import sonia.scm.NotFoundException; +import sonia.scm.SCMContextProvider; +import sonia.scm.util.Util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; + +@Singleton @EagerSingleton +public class DefaultRepositoryRoleManager extends AbstractRepositoryRoleManager +{ + + /** the logger for XmlRepositoryRoleManager */ + private static final Logger logger = + LoggerFactory.getLogger(DefaultRepositoryRoleManager.class); + + @Inject + public DefaultRepositoryRoleManager(RepositoryRoleDAO repositoryRoleDAO) + { + this.repositoryRoleDAO = repositoryRoleDAO; + this.managerDaoAdapter = new ManagerDaoAdapter<>(repositoryRoleDAO); + } + + @Override + public void close() { + // do nothing + } + + @Override + public RepositoryRole create(RepositoryRole repositoryRole) { + String type = repositoryRole.getType(); + if (Util.isEmpty(type)) { + repositoryRole.setType(repositoryRoleDAO.getType()); + } + + logger.info("create repositoryRole {} of type {}", repositoryRole.getName(), repositoryRole.getType()); + + return managerDaoAdapter.create( + repositoryRole, + RepositoryRolePermissions::modify, + newRepositoryRole -> fireEvent(HandlerEventType.BEFORE_CREATE, newRepositoryRole), + newRepositoryRole -> fireEvent(HandlerEventType.CREATE, newRepositoryRole) + ); + } + + @Override + public void delete(RepositoryRole repositoryRole) { + logger.info("delete repositoryRole {} of type {}", repositoryRole.getName(), repositoryRole.getType()); + managerDaoAdapter.delete( + repositoryRole, + RepositoryRolePermissions::modify, + toDelete -> fireEvent(HandlerEventType.BEFORE_DELETE, toDelete), + toDelete -> fireEvent(HandlerEventType.DELETE, toDelete) + ); + } + + @Override + public void init(SCMContextProvider context) { + } + + @Override + public void modify(RepositoryRole repositoryRole) { + logger.info("modify repositoryRole {} of type {}", repositoryRole.getName(), repositoryRole.getType()); + managerDaoAdapter.modify( + repositoryRole, + x -> RepositoryRolePermissions.modify(), + notModified -> fireEvent(HandlerEventType.BEFORE_MODIFY, repositoryRole, notModified), + notModified -> fireEvent(HandlerEventType.MODIFY, repositoryRole, notModified)); + } + + @Override + public void refresh(RepositoryRole repositoryRole) { + logger.info("refresh repositoryRole {} of type {}", repositoryRole.getName(), repositoryRole.getType()); + + RepositoryRolePermissions.read().check(); + RepositoryRole fresh = repositoryRoleDAO.get(repositoryRole.getName()); + + if (fresh == null) { + throw new NotFoundException(RepositoryRole.class, repositoryRole.getName()); + } + } + + @Override + public RepositoryRole get(String id) { + RepositoryRolePermissions.read(); + + RepositoryRole repositoryRole = repositoryRoleDAO.get(id); + + if (repositoryRole != null) { + return repositoryRole.clone(); + } else { + return null; + } + } + + @Override + public Collection getAll() { + return getAll(repositoryRole -> true, null); + } + + @Override + public Collection getAll(Predicate filter, Comparator comparator) { + List repositoryRoles = new ArrayList<>(); + + if (!RepositoryRolePermissions.read().isPermitted()) { + return Collections.emptySet(); + } + for (RepositoryRole repositoryRole : repositoryRoleDAO.getAll()) { + repositoryRoles.add(repositoryRole.clone()); + } + + if (comparator != null) { + Collections.sort(repositoryRoles, comparator); + } + + return repositoryRoles; + } + + @Override + public Collection getAll(Comparator comaparator, int start, int limit) { + if (!RepositoryRolePermissions.read().isPermitted()) { + return Collections.emptySet(); + } + return Util.createSubCollection(repositoryRoleDAO.getAll(), comaparator, + (collection, item) -> { + collection.add(item.clone()); + }, start, limit); + } + + @Override + public Collection getAll(int start, int limit) + { + return getAll(null, start, limit); + } + + @Override + public Long getLastModified() + { + return repositoryRoleDAO.getLastModified(); + } + + private final RepositoryRoleDAO repositoryRoleDAO; + private final ManagerDaoAdapter managerDaoAdapter; +}