mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 14:35:45 +01:00
merge
This commit is contained in:
@@ -1,168 +0,0 @@
|
||||
/**
|
||||
* 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.api.rest;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.Serializable;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@XmlRootElement
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class Permission implements Serializable
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = 4320217034601679261L;
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
public Permission() {}
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
* @param value
|
||||
*/
|
||||
public Permission(String id, String value)
|
||||
{
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param obj
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final Permission other = (Permission) obj;
|
||||
|
||||
return Objects.equal(id, other.id) && Objects.equal(value, other.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(id, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
//J-
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("id", id)
|
||||
.add("value", value)
|
||||
.toString();
|
||||
//J+
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private String id;
|
||||
|
||||
/** Field description */
|
||||
private String value;
|
||||
}
|
||||
@@ -35,6 +35,8 @@ package sonia.scm.api.rest.resources;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.net.UrlEscapers;
|
||||
import org.apache.commons.beanutils.BeanComparator;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.slf4j.Logger;
|
||||
@@ -45,7 +47,6 @@ import sonia.scm.ModelObject;
|
||||
import sonia.scm.PageResult;
|
||||
import sonia.scm.api.rest.RestExceptionResult;
|
||||
import sonia.scm.util.AssertUtil;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
import javax.ws.rs.core.CacheControl;
|
||||
@@ -63,6 +64,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.net.URI;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
@@ -139,11 +141,7 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
|
||||
manager.create(item);
|
||||
|
||||
String id = getId(item);
|
||||
|
||||
id = HttpUtil.encode(id);
|
||||
response = Response.created(
|
||||
uriInfo.getAbsolutePath().resolve(
|
||||
getPathPart().concat("/").concat(id))).build();
|
||||
response = Response.created(location(uriInfo, id)).build();
|
||||
}
|
||||
catch (AuthorizationException ex)
|
||||
{
|
||||
@@ -159,6 +157,12 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
|
||||
return response;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
URI location(UriInfo uriInfo, String id) {
|
||||
String escaped = UrlEscapers.urlPathSegmentEscaper().escape(id);
|
||||
return uriInfo.getAbsolutePath().resolve(getPathPart().concat("/").concat(escaped));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -247,7 +251,7 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
|
||||
*/
|
||||
public Response get(Request request, String id)
|
||||
{
|
||||
Response response = null;
|
||||
Response response;
|
||||
T item = manager.get(id);
|
||||
|
||||
if (item != null)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import sonia.scm.security.RepositoryRole;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class AvailableRepositoryPermissionsDto extends HalRepresentation {
|
||||
private final Collection<String> availableVerbs;
|
||||
private final Collection<RepositoryRole> availableRoles;
|
||||
|
||||
public AvailableRepositoryPermissionsDto(Collection<String> availableVerbs, Collection<RepositoryRole> availableRoles) {
|
||||
this.availableVerbs = availableVerbs;
|
||||
this.availableRoles = availableRoles;
|
||||
}
|
||||
|
||||
public Collection<String> getAvailableVerbs() {
|
||||
return availableVerbs;
|
||||
}
|
||||
|
||||
public Collection<RepositoryRole> getAvailableRoles() {
|
||||
return availableRoles;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -12,8 +13,7 @@ public class BranchDto extends HalRepresentation {
|
||||
private String name;
|
||||
private String revision;
|
||||
|
||||
@Override
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
BranchDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
|
||||
@@ -15,7 +15,7 @@ import static de.otto.edison.hal.Link.linkBuilder;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@Mapper
|
||||
public abstract class BranchToBranchDtoMapper extends LinkAppenderMapper {
|
||||
public abstract class BranchToBranchDtoMapper extends HalAppenderMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
@@ -23,16 +23,17 @@ public abstract class BranchToBranchDtoMapper extends LinkAppenderMapper {
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract BranchDto map(Branch branch, @Context NamespaceAndName namespaceAndName);
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(Branch source, @MappingTarget BranchDto target, @Context NamespaceAndName namespaceAndName) {
|
||||
@ObjectFactory
|
||||
BranchDto createDto(@Context NamespaceAndName namespaceAndName, Branch branch) {
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
.self(resourceLinks.branch().self(namespaceAndName, target.getName()))
|
||||
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, target.getName())).build())
|
||||
.single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build())
|
||||
.single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())).build());
|
||||
.self(resourceLinks.branch().self(namespaceAndName, branch.getName()))
|
||||
.single(linkBuilder("history", resourceLinks.branch().history(namespaceAndName, branch.getName())).build())
|
||||
.single(linkBuilder("changeset", resourceLinks.changeset().changeset(namespaceAndName.getNamespace(), namespaceAndName.getName(), branch.getRevision())).build())
|
||||
.single(linkBuilder("source", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), branch.getRevision())).build());
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), source, namespaceAndName);
|
||||
Embedded.Builder embeddedBuilder = Embedded.embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), branch, namespaceAndName);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new BranchDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ public class ConfigDto extends HalRepresentation {
|
||||
private boolean disableGroupingGrid;
|
||||
private String dateFormat;
|
||||
private boolean anonymousAccessEnabled;
|
||||
@NoBlankStrings
|
||||
private Set<String> adminGroups;
|
||||
@NoBlankStrings
|
||||
private Set<String> adminUsers;
|
||||
private String baseUrl;
|
||||
private boolean forceBaseUrl;
|
||||
|
||||
@@ -9,6 +9,7 @@ import sonia.scm.util.ScmConfigurationUtil;
|
||||
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.PUT;
|
||||
@@ -71,7 +72,7 @@ public class ConfigResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
public Response update(ConfigDto configDto) {
|
||||
public Response update(@Valid ConfigDto configDto) {
|
||||
|
||||
// This *could* be moved to ScmConfiguration or ScmConfigurationUtil classes.
|
||||
// But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later.
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class EdisonLinkAppender implements LinkAppender {
|
||||
class EdisonHalAppender implements HalAppender {
|
||||
|
||||
private final Links.Builder builder;
|
||||
private final Links.Builder linkBuilder;
|
||||
private final Embedded.Builder embeddedBuilder;
|
||||
|
||||
EdisonLinkAppender(Links.Builder builder) {
|
||||
this.builder = builder;
|
||||
EdisonHalAppender(Links.Builder linkBuilder, Embedded.Builder embeddedBuilder) {
|
||||
this.linkBuilder = linkBuilder;
|
||||
this.embeddedBuilder = embeddedBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendOne(String rel, String href) {
|
||||
builder.single(Link.link(rel, href));
|
||||
public void appendLink(String rel, String href) {
|
||||
linkBuilder.single(Link.link(rel, href));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkArrayBuilder arrayBuilder(String rel) {
|
||||
return new EdisonLinkArrayBuilder(builder, rel);
|
||||
public LinkArrayBuilder linkArrayBuilder(String rel) {
|
||||
return new EdisonLinkArrayBuilder(linkBuilder, rel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendEmbedded(String rel, HalRepresentation embedded) {
|
||||
embeddedBuilder.with(rel, embedded);
|
||||
}
|
||||
|
||||
private static class EdisonLinkArrayBuilder implements LinkArrayBuilder {
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -27,10 +28,8 @@ public class FileObjectDto extends HalRepresentation {
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private String revision;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
public FileObjectDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
|
||||
public void setChildren(List<FileObjectDto> children) {
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.FileObject;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.SubRepository;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
|
||||
@Mapper
|
||||
public abstract class FileObjectToFileObjectDtoMapper extends LinkAppenderMapper implements InstantAttributeMapper {
|
||||
public abstract class FileObjectToFileObjectDtoMapper extends HalAppenderMapper implements InstantAttributeMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
@@ -28,20 +26,21 @@ public abstract class FileObjectToFileObjectDtoMapper extends LinkAppenderMapper
|
||||
|
||||
abstract SubRepositoryDto mapSubrepository(SubRepository subRepository);
|
||||
|
||||
@AfterMapping
|
||||
void addLinks(FileObject fileObject, @MappingTarget FileObjectDto dto, @Context NamespaceAndName namespaceAndName, @Context String revision) {
|
||||
@ObjectFactory
|
||||
FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context String revision, FileObject fileObject) {
|
||||
String path = removeFirstSlash(fileObject.getPath());
|
||||
Links.Builder links = Links.linkingTo();
|
||||
if (dto.isDirectory()) {
|
||||
if (fileObject.isDirectory()) {
|
||||
links.self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path));
|
||||
} else {
|
||||
links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path));
|
||||
links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path)));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(links), fileObject, namespaceAndName, revision);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), fileObject, namespaceAndName, revision);
|
||||
|
||||
dto.add(links.build());
|
||||
return new FileObjectDto(links.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
private String removeFirstSlash(String source) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -28,13 +29,7 @@ public class GroupDto extends HalRepresentation {
|
||||
private Map<String, String> properties;
|
||||
private List<String> members;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
}
|
||||
|
||||
public HalRepresentation withMembers(List<MemberDto> members) {
|
||||
return super.withEmbedded("members", members);
|
||||
GroupDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.GroupPermissions;
|
||||
import sonia.scm.security.PermissionPermissions;
|
||||
@@ -12,6 +12,7 @@ import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@@ -23,28 +24,26 @@ public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto>
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(Group group, @MappingTarget GroupDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.group().self(target.getName()));
|
||||
@ObjectFactory
|
||||
GroupDto createDto(Group group) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.group().self(group.getName()));
|
||||
if (GroupPermissions.delete(group).isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.group().delete(target.getName())));
|
||||
linksBuilder.single(link("delete", resourceLinks.group().delete(group.getName())));
|
||||
}
|
||||
if (GroupPermissions.modify(group).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.group().update(target.getName())));
|
||||
linksBuilder.single(link("update", resourceLinks.group().update(group.getName())));
|
||||
}
|
||||
if (PermissionPermissions.read().isPermitted()) {
|
||||
linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(target.getName())));
|
||||
linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(group.getName())));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), group);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
}
|
||||
|
||||
@AfterMapping
|
||||
void mapMembers(Group group, @MappingTarget GroupDto target) {
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
List<MemberDto> memberDtos = group.getMembers().stream().map(this::createMember).collect(Collectors.toList());
|
||||
target.withMembers(memberDtos);
|
||||
embeddedBuilder.with("members", memberDtos);
|
||||
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), group);
|
||||
|
||||
return new GroupDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
private MemberDto createMember(String name) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -9,8 +10,8 @@ public class IndexDto extends HalRepresentation {
|
||||
|
||||
private final String version;
|
||||
|
||||
IndexDto(String version, Links links) {
|
||||
super(links);
|
||||
IndexDto(Links links, Embedded embedded, String version) {
|
||||
super(links, embedded);
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@@ -13,9 +14,10 @@ import sonia.scm.user.UserPermissions;
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
|
||||
public class IndexDtoGenerator extends LinkAppenderMapper {
|
||||
public class IndexDtoGenerator extends HalAppenderMapper {
|
||||
|
||||
private final ResourceLinks resourceLinks;
|
||||
private final SCMContextProvider scmContextProvider;
|
||||
@@ -56,12 +58,14 @@ public class IndexDtoGenerator extends LinkAppenderMapper {
|
||||
if (PermissionPermissions.list().isPermitted()) {
|
||||
builder.single(link("permissions", resourceLinks.permissions().self()));
|
||||
}
|
||||
builder.single(link("availableRepositoryPermissions", resourceLinks.availableRepositoryPermissions().self()));
|
||||
} else {
|
||||
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(builder), new Index());
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(builder, embeddedBuilder), new Index());
|
||||
|
||||
return new IndexDto(scmContextProvider.getVersion(), builder.build());
|
||||
return new IndexDto(builder.build(), embeddedBuilder.build(), scmContextProvider.getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,30 +10,30 @@ import javax.servlet.ServletContextListener;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Registers every {@link LinkEnricher} which is annotated with an {@link Enrich} annotation.
|
||||
* Registers every {@link HalEnricher} which is annotated with an {@link Enrich} annotation.
|
||||
*/
|
||||
@Extension
|
||||
public class LinkEnricherAutoRegistration implements ServletContextListener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LinkEnricherAutoRegistration.class);
|
||||
|
||||
private final LinkEnricherRegistry registry;
|
||||
private final Set<LinkEnricher> enrichers;
|
||||
private final HalEnricherRegistry registry;
|
||||
private final Set<HalEnricher> enrichers;
|
||||
|
||||
@Inject
|
||||
public LinkEnricherAutoRegistration(LinkEnricherRegistry registry, Set<LinkEnricher> enrichers) {
|
||||
public LinkEnricherAutoRegistration(HalEnricherRegistry registry, Set<HalEnricher> enrichers) {
|
||||
this.registry = registry;
|
||||
this.enrichers = enrichers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
for (LinkEnricher enricher : enrichers) {
|
||||
for (HalEnricher enricher : enrichers) {
|
||||
Enrich annotation = enricher.getClass().getAnnotation(Enrich.class);
|
||||
if (annotation != null) {
|
||||
registry.register(annotation.value(), enricher);
|
||||
} else {
|
||||
LOG.warn("found LinkEnricher extension {} without Enrich annotation", enricher.getClass());
|
||||
LOG.warn("found HalEnricher extension {} without Enrich annotation", enricher.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class MapperModule extends AbstractModule {
|
||||
bind(RepositoryTypeCollectionToDtoMapper.class);
|
||||
|
||||
bind(BranchToBranchDtoMapper.class).to(Mappers.getMapper(BranchToBranchDtoMapper.class).getClass());
|
||||
bind(PermissionDtoToPermissionMapper.class).to(Mappers.getMapper(PermissionDtoToPermissionMapper.class).getClass());
|
||||
bind(RepositoryPermissionDtoToRepositoryPermissionMapper.class).to(Mappers.getMapper(RepositoryPermissionDtoToRepositoryPermissionMapper.class).getClass());
|
||||
bind(RepositoryPermissionToRepositoryPermissionDtoMapper.class).to(Mappers.getMapper(RepositoryPermissionToRepositoryPermissionDtoMapper.class).getClass());
|
||||
|
||||
bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(DefaultChangesetToChangesetDtoMapper.class).getClass());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -18,9 +19,7 @@ public class MeDto extends HalRepresentation {
|
||||
private String mail;
|
||||
private List<String> groups;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
MeDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
@@ -13,10 +14,11 @@ import sonia.scm.user.UserPermissions;
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collections;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
public class MeDtoFactory extends LinkAppenderMapper {
|
||||
public class MeDtoFactory extends HalAppenderMapper {
|
||||
|
||||
private final ResourceLinks resourceLinks;
|
||||
private final UserManager userManager;
|
||||
@@ -29,15 +31,11 @@ public class MeDtoFactory extends LinkAppenderMapper {
|
||||
|
||||
public MeDto create() {
|
||||
PrincipalCollection principals = getPrincipalCollection();
|
||||
|
||||
MeDto dto = new MeDto();
|
||||
|
||||
User user = principals.oneByType(User.class);
|
||||
|
||||
MeDto dto = createDto(user);
|
||||
mapUserProperties(user, dto);
|
||||
mapGroups(principals, dto);
|
||||
|
||||
appendLinks(user, dto);
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -61,21 +59,22 @@ public class MeDtoFactory extends LinkAppenderMapper {
|
||||
}
|
||||
|
||||
|
||||
private void appendLinks(User user, MeDto target) {
|
||||
private MeDto createDto(User user) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
|
||||
if (UserPermissions.delete(user).isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.me().delete(target.getName())));
|
||||
linksBuilder.single(link("delete", resourceLinks.me().delete(user.getName())));
|
||||
}
|
||||
if (UserPermissions.modify(user).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.me().update(target.getName())));
|
||||
linksBuilder.single(link("update", resourceLinks.me().update(user.getName())));
|
||||
}
|
||||
if (userManager.isTypeDefault(user) && UserPermissions.changePassword(user).isPermitted()) {
|
||||
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), new Me(), user);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), new Me(), user);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new MeDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
|
||||
@Retention(RUNTIME)
|
||||
@Constraint(validatedBy = NoBlankStringsValidator.class)
|
||||
@Documented
|
||||
public @interface NoBlankStrings {
|
||||
|
||||
String message() default "collection must not contain empty strings";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.Collection;
|
||||
|
||||
public class NoBlankStringsValidator implements ConstraintValidator<NoBlankStrings, Collection> {
|
||||
|
||||
@Override
|
||||
public void initialize(NoBlankStrings constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Collection object, ConstraintValidatorContext constraintContext) {
|
||||
if ( object == null || object.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return object.stream()
|
||||
.map(x -> x.toString())
|
||||
.map(s -> ((String) s).trim())
|
||||
.noneMatch(s -> ((String) s).isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,9 @@ 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.SecurityUtils;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.repository.PermissionType;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
@@ -24,6 +23,7 @@ import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
public class RepositoryCollectionResource {
|
||||
@@ -100,7 +100,7 @@ public class RepositoryCollectionResource {
|
||||
|
||||
private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) {
|
||||
Repository repository = dtoToRepositoryMapper.map(repositoryDto, null);
|
||||
repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), PermissionType.OWNER)));
|
||||
repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), singletonList("*"), false)));
|
||||
return repository;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
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 org.hibernate.validator.constraints.Email;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
@@ -13,7 +15,7 @@ import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter @Setter
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class RepositoryDto extends HalRepresentation {
|
||||
|
||||
@Email
|
||||
@@ -31,9 +33,7 @@ public class RepositoryDto extends HalRepresentation {
|
||||
private String type;
|
||||
protected Map<String, String> properties;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
RepositoryDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ public abstract class RepositoryDtoToRepositoryMapper extends BaseDtoMapper {
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "publicReadable", ignore = true)
|
||||
@Mapping(target = "healthCheckFailures", ignore = true)
|
||||
@Mapping(target = "permissions", ignore = true)
|
||||
public abstract Repository map(RepositoryDto repositoryDto, @Context String id);
|
||||
|
||||
@AfterMapping
|
||||
|
||||
@@ -7,9 +7,13 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
|
||||
|
||||
@Getter @Setter @ToString @NoArgsConstructor
|
||||
@@ -20,16 +24,8 @@ public class RepositoryPermissionDto extends HalRepresentation {
|
||||
@Pattern(regexp = USER_GROUP_PATTERN)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* the type can be replaced with a dto enum if the mapstruct 1.3.0 is stable
|
||||
* the mapstruct has a Bug on mapping enums in the 1.2.0-Final Version
|
||||
*
|
||||
* see the bug fix: https://github.com/mapstruct/mapstruct/commit/460e87eef6eb71245b387fdb0509c726676a8e19
|
||||
*
|
||||
**/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String type;
|
||||
|
||||
@NotEmpty
|
||||
private Collection<String> verbs;
|
||||
|
||||
private boolean groupPermission = false;
|
||||
|
||||
@@ -38,7 +34,6 @@ public class RepositoryPermissionDto extends HalRepresentation {
|
||||
this.groupPermission = groupPermission;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.mapstruct.CollectionMappingStrategy;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
|
||||
@Mapper
|
||||
public abstract class PermissionDtoToPermissionMapper {
|
||||
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE)
|
||||
public abstract class RepositoryPermissionDtoToRepositoryPermissionMapper {
|
||||
|
||||
public abstract RepositoryPermission map(RepositoryPermissionDto permissionDto);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import de.otto.edison.hal.Links;
|
||||
import sonia.scm.security.RepositoryPermissionProvider;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
|
||||
/**
|
||||
* RESTful Web Service Resource to get available repository types.
|
||||
*/
|
||||
@Path(RepositoryPermissionResource.PATH)
|
||||
public class RepositoryPermissionResource {
|
||||
|
||||
static final String PATH = "v2/repositoryPermissions/";
|
||||
|
||||
private final RepositoryPermissionProvider repositoryPermissionProvider;
|
||||
private final ResourceLinks resourceLinks;
|
||||
|
||||
@Inject
|
||||
public RepositoryPermissionResource(RepositoryPermissionProvider repositoryPermissionProvider, ResourceLinks resourceLinks) {
|
||||
this.repositoryPermissionProvider = repositoryPermissionProvider;
|
||||
this.resourceLinks = resourceLinks;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces(VndMediaType.REPOSITORY_PERMISSION_COLLECTION)
|
||||
public AvailableRepositoryPermissionsDto get() {
|
||||
AvailableRepositoryPermissionsDto dto = new AvailableRepositoryPermissionsDto(repositoryPermissionProvider.availableVerbs(), repositoryPermissionProvider.availableRoles());
|
||||
dto.add(Links.linkingTo().self(resourceLinks.availableRepositoryPermissions().self()).build());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -35,18 +35,21 @@ import static sonia.scm.NotFoundException.notFound;
|
||||
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
|
||||
|
||||
@Slf4j
|
||||
public class PermissionRootResource {
|
||||
public class RepositoryPermissionRootResource {
|
||||
|
||||
|
||||
private PermissionDtoToPermissionMapper dtoToModelMapper;
|
||||
private RepositoryPermissionDtoToRepositoryPermissionMapper dtoToModelMapper;
|
||||
private RepositoryPermissionToRepositoryPermissionDtoMapper modelToDtoMapper;
|
||||
private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper;
|
||||
private ResourceLinks resourceLinks;
|
||||
private final RepositoryManager manager;
|
||||
|
||||
|
||||
@Inject
|
||||
public PermissionRootResource(PermissionDtoToPermissionMapper dtoToModelMapper, RepositoryPermissionToRepositoryPermissionDtoMapper modelToDtoMapper, RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper, ResourceLinks resourceLinks, RepositoryManager manager) {
|
||||
public RepositoryPermissionRootResource(
|
||||
RepositoryPermissionDtoToRepositoryPermissionMapper dtoToModelMapper,
|
||||
RepositoryPermissionToRepositoryPermissionDtoMapper modelToDtoMapper,
|
||||
RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper,
|
||||
ResourceLinks resourceLinks,
|
||||
RepositoryManager manager) {
|
||||
this.dtoToModelMapper = dtoToModelMapper;
|
||||
this.modelToDtoMapper = modelToDtoMapper;
|
||||
this.repositoryPermissionCollectionToDtoMapper = repositoryPermissionCollectionToDtoMapper;
|
||||
@@ -54,7 +57,6 @@ public class PermissionRootResource {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new permission to the user or group managed by the repository
|
||||
*
|
||||
@@ -71,9 +73,9 @@ public class PermissionRootResource {
|
||||
@ResponseCode(code = 409, condition = "conflict")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes(VndMediaType.PERMISSION)
|
||||
@Consumes(VndMediaType.REPOSITORY_PERMISSION)
|
||||
@Path("")
|
||||
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid RepositoryPermissionDto permission) {
|
||||
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryPermissionDto permission) {
|
||||
log.info("try to add new permission: {}", permission);
|
||||
Repository repository = load(namespace, name);
|
||||
RepositoryPermissions.permissionWrite(repository).check();
|
||||
@@ -84,7 +86,6 @@ public class PermissionRootResource {
|
||||
return Response.created(URI.create(resourceLinks.repositoryPermission().self(namespace, name, urlPermissionName))).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the searched permission with permission name related to a repository
|
||||
*
|
||||
@@ -99,7 +100,7 @@ public class PermissionRootResource {
|
||||
@ResponseCode(code = 404, condition = "not found"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces(VndMediaType.PERMISSION)
|
||||
@Produces(VndMediaType.REPOSITORY_PERMISSION)
|
||||
@TypeHint(RepositoryPermissionDto.class)
|
||||
@Path("{permission-name}")
|
||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName) {
|
||||
@@ -115,7 +116,6 @@ public class PermissionRootResource {
|
||||
).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all permissions related to a repository
|
||||
*
|
||||
@@ -130,7 +130,7 @@ public class PermissionRootResource {
|
||||
@ResponseCode(code = 404, condition = "not found"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@Produces(VndMediaType.PERMISSION)
|
||||
@Produces(VndMediaType.REPOSITORY_PERMISSION)
|
||||
@TypeHint(RepositoryPermissionDto.class)
|
||||
@Path("")
|
||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) {
|
||||
@@ -139,7 +139,6 @@ public class PermissionRootResource {
|
||||
return Response.ok(repositoryPermissionCollectionToDtoMapper.map(repository)).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update a permission to the user or group managed by the repository
|
||||
* ignore the user input for groupPermission and take it from the path parameter (if the group prefix (@) exists it is a group permission)
|
||||
@@ -155,7 +154,7 @@ public class PermissionRootResource {
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes(VndMediaType.PERMISSION)
|
||||
@Consumes(VndMediaType.REPOSITORY_PERMISSION)
|
||||
@Path("{permission-name}")
|
||||
public Response update(@PathParam("namespace") String namespace,
|
||||
@PathParam("name") String name,
|
||||
@@ -172,6 +171,7 @@ public class PermissionRootResource {
|
||||
if (!extractedPermissionName.equals(permission.getName())) {
|
||||
checkPermissionAlreadyExists(permission, repository);
|
||||
}
|
||||
|
||||
RepositoryPermission existingPermission = repository.getPermissions()
|
||||
.stream()
|
||||
.filter(filterPermission(permissionName))
|
||||
@@ -208,17 +208,16 @@ public class PermissionRootResource {
|
||||
.stream()
|
||||
.filter(filterPermission(permissionName))
|
||||
.findFirst()
|
||||
.ifPresent(repository::removePermission)
|
||||
;
|
||||
.ifPresent(repository::removePermission);
|
||||
manager.modify(repository);
|
||||
log.info("the permission with name: {} is updated.", permissionName);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
Predicate<RepositoryPermission> filterPermission(String permissionName) {
|
||||
return permission -> getPermissionName(permissionName).equals(permission.getName())
|
||||
private Predicate<RepositoryPermission> filterPermission(String name) {
|
||||
return permission -> getPermissionName(name).equals(permission.getName())
|
||||
&&
|
||||
permission.isGroupPermission() == isGroupPermission(permissionName);
|
||||
permission.isGroupPermission() == isGroupPermission(name);
|
||||
}
|
||||
|
||||
private String getPermissionName(String permissionName) {
|
||||
@@ -231,7 +230,6 @@ public class PermissionRootResource {
|
||||
return permissionName.startsWith(GROUP_PREFIX);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if the actual user is permitted to manage the repository permissions
|
||||
* return the repository if the user is permitted
|
||||
@@ -39,7 +39,7 @@ public class RepositoryResource {
|
||||
private final Provider<ChangesetRootResource> changesetRootResource;
|
||||
private final Provider<SourceRootResource> sourceRootResource;
|
||||
private final Provider<ContentResource> contentResource;
|
||||
private final Provider<PermissionRootResource> permissionRootResource;
|
||||
private final Provider<RepositoryPermissionRootResource> permissionRootResource;
|
||||
private final Provider<DiffRootResource> diffRootResource;
|
||||
private final Provider<ModificationsRootResource> modificationsRootResource;
|
||||
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
|
||||
@@ -54,7 +54,7 @@ public class RepositoryResource {
|
||||
Provider<BranchRootResource> branchRootResource,
|
||||
Provider<ChangesetRootResource> changesetRootResource,
|
||||
Provider<SourceRootResource> sourceRootResource, Provider<ContentResource> contentResource,
|
||||
Provider<PermissionRootResource> permissionRootResource,
|
||||
Provider<RepositoryPermissionRootResource> permissionRootResource,
|
||||
Provider<DiffRootResource> diffRootResource,
|
||||
Provider<ModificationsRootResource> modificationsRootResource,
|
||||
Provider<FileHistoryRootResource> fileHistoryRootResource,
|
||||
@@ -154,7 +154,6 @@ public class RepositoryResource {
|
||||
|
||||
private Repository processUpdate(RepositoryDto repositoryDto, Repository existing) {
|
||||
Repository changedRepository = dtoToRepositoryMapper.map(repositoryDto, existing.getId());
|
||||
changedRepository.setPermissions(existing.getPermissions());
|
||||
return changedRepository;
|
||||
}
|
||||
|
||||
@@ -194,7 +193,7 @@ public class RepositoryResource {
|
||||
}
|
||||
|
||||
@Path("permissions/")
|
||||
public PermissionRootResource permissions() {
|
||||
public RepositoryPermissionRootResource permissions() {
|
||||
return permissionRootResource.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.HealthCheckFailure;
|
||||
import sonia.scm.repository.Repository;
|
||||
@@ -17,6 +17,7 @@ import sonia.scm.repository.api.ScmProtocol;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
@@ -33,17 +34,17 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
|
||||
|
||||
abstract HealthCheckFailureDto toDto(HealthCheckFailure failure);
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(Repository repository, @MappingTarget RepositoryDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(target.getNamespace(), target.getName()));
|
||||
@ObjectFactory
|
||||
RepositoryDto createDto(Repository repository) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(repository.getNamespace(), repository.getName()));
|
||||
if (RepositoryPermissions.delete(repository).isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.repository().delete(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("delete", resourceLinks.repository().delete(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (RepositoryPermissions.modify(repository).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.repository().update(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("update", resourceLinks.repository().update(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (RepositoryPermissions.permissionRead(repository).isPermitted()) {
|
||||
linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
|
||||
if (RepositoryPermissions.pull(repository).isPermitted()) {
|
||||
@@ -53,26 +54,27 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
|
||||
linksBuilder.array(protocolLinks);
|
||||
}
|
||||
if (repositoryService.isSupported(Command.TAGS)) {
|
||||
linksBuilder.single(link("tags", resourceLinks.tag().all(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("tags", resourceLinks.tag().all(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (repositoryService.isSupported(Command.BRANCHES)) {
|
||||
linksBuilder.single(link("branches", resourceLinks.branchCollection().self(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("branches", resourceLinks.branchCollection().self(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (repositoryService.isSupported(Feature.INCOMING_REVISION)) {
|
||||
linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("incomingChangesets", resourceLinks.incoming().changesets(repository.getNamespace(), repository.getName())));
|
||||
linksBuilder.single(link("incomingDiff", resourceLinks.incoming().diff(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
if (repositoryService.isSupported(Command.MERGE)) {
|
||||
linksBuilder.single(link("merge", resourceLinks.merge().merge(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("mergeDryRun", resourceLinks.merge().dryRun(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("merge", resourceLinks.merge().merge(repository.getNamespace(), repository.getName())));
|
||||
linksBuilder.single(link("mergeDryRun", resourceLinks.merge().dryRun(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
}
|
||||
linksBuilder.single(link("changesets", resourceLinks.changeset().all(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName())));
|
||||
linksBuilder.single(link("changesets", resourceLinks.changeset().all(repository.getNamespace(), repository.getName())));
|
||||
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(repository.getNamespace(), repository.getName())));
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), repository);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), repository);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new RepositoryDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
private Link createProtocolLink(ScmProtocol protocol) {
|
||||
|
||||
@@ -514,7 +514,7 @@ class ResourceLinks {
|
||||
private final LinkBuilder permissionLinkBuilder;
|
||||
|
||||
RepositoryPermissionLinks(ScmPathInfo pathInfo) {
|
||||
permissionLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, PermissionRootResource.class);
|
||||
permissionLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, RepositoryPermissionRootResource.class);
|
||||
}
|
||||
|
||||
String all(String namespace, String name) {
|
||||
@@ -639,14 +639,30 @@ class ResourceLinks {
|
||||
}
|
||||
|
||||
static class PermissionsLinks {
|
||||
private final LinkBuilder permissionsLlinkBuilder;
|
||||
private final LinkBuilder permissionsLinkBuilder;
|
||||
|
||||
PermissionsLinks(ScmPathInfo scmPathInfo) {
|
||||
this.permissionsLlinkBuilder = new LinkBuilder(scmPathInfo, GlobalPermissionResource.class);
|
||||
this.permissionsLinkBuilder = new LinkBuilder(scmPathInfo, GlobalPermissionResource.class);
|
||||
}
|
||||
|
||||
String self() {
|
||||
return permissionsLlinkBuilder.method("getAll").parameters().href();
|
||||
return permissionsLinkBuilder.method("getAll").parameters().href();
|
||||
}
|
||||
}
|
||||
|
||||
public AvailableRepositoryPermissionLinks availableRepositoryPermissions() {
|
||||
return new AvailableRepositoryPermissionLinks(scmPathInfoStore.get());
|
||||
}
|
||||
|
||||
static class AvailableRepositoryPermissionLinks {
|
||||
private final LinkBuilder linkBuilder;
|
||||
|
||||
AvailableRepositoryPermissionLinks(ScmPathInfo scmPathInfo) {
|
||||
this.linkBuilder = new LinkBuilder(scmPathInfo, RepositoryPermissionResource.class);
|
||||
}
|
||||
|
||||
String self() {
|
||||
return linkBuilder.method("get").parameters().href();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -15,10 +16,8 @@ public class TagDto extends HalRepresentation {
|
||||
|
||||
private String revision;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
TagDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Tag;
|
||||
|
||||
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;
|
||||
|
||||
@Mapper
|
||||
public abstract class TagToTagDtoMapper extends LinkAppenderMapper {
|
||||
public abstract class TagToTagDtoMapper extends HalAppenderMapper {
|
||||
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
@@ -23,15 +24,16 @@ public abstract class TagToTagDtoMapper extends LinkAppenderMapper {
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName);
|
||||
|
||||
@AfterMapping
|
||||
void appendLinks(Tag tag, @MappingTarget TagDto target, @Context NamespaceAndName namespaceAndName) {
|
||||
@ObjectFactory
|
||||
TagDto createDto(@Context NamespaceAndName namespaceAndName, Tag tag) {
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
.self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName()))
|
||||
.single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())))
|
||||
.single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())));
|
||||
.self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getName()))
|
||||
.single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision())))
|
||||
.single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision())));
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), tag, namespaceAndName);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), tag, namespaceAndName);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new TagDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
@@ -33,9 +34,7 @@ public class UserDto extends HalRepresentation {
|
||||
private String type;
|
||||
private Map<String, String> properties;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||
protected HalRepresentation add(Links links) {
|
||||
return super.add(links);
|
||||
UserDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.security.PermissionPermissions;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
@@ -12,6 +12,7 @@ import sonia.scm.user.UserPermissions;
|
||||
|
||||
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;
|
||||
|
||||
@@ -31,25 +32,26 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
@AfterMapping
|
||||
protected void appendLinks(User user, @MappingTarget UserDto target) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(target.getName()));
|
||||
@ObjectFactory
|
||||
UserDto createDto(User user) {
|
||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(user.getName()));
|
||||
if (UserPermissions.delete(user).isPermitted()) {
|
||||
linksBuilder.single(link("delete", resourceLinks.user().delete(target.getName())));
|
||||
linksBuilder.single(link("delete", resourceLinks.user().delete(user.getName())));
|
||||
}
|
||||
if (UserPermissions.modify(user).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.user().update(target.getName())));
|
||||
linksBuilder.single(link("update", resourceLinks.user().update(user.getName())));
|
||||
if (userManager.isTypeDefault(user)) {
|
||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
|
||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(user.getName())));
|
||||
}
|
||||
}
|
||||
if (PermissionPermissions.read().isPermitted()) {
|
||||
linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(target.getName())));
|
||||
linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(user.getName())));
|
||||
}
|
||||
|
||||
appendLinks(new EdisonLinkAppender(linksBuilder), user);
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), user);
|
||||
|
||||
target.add(linksBuilder.build());
|
||||
return new UserDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -198,8 +198,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
private void collectRepositoryPermissions(Builder<String> builder,
|
||||
Repository repository, User user, GroupNames groups)
|
||||
{
|
||||
Collection<RepositoryPermission> repositoryPermissions
|
||||
= repository.getPermissions();
|
||||
Collection<RepositoryPermission> repositoryPermissions = repository.getPermissions();
|
||||
|
||||
if (Util.isNotEmpty(repositoryPermissions))
|
||||
{
|
||||
@@ -207,9 +206,9 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
for (RepositoryPermission permission : repositoryPermissions)
|
||||
{
|
||||
hasPermission = isUserPermitted(user, groups, permission);
|
||||
if (hasPermission)
|
||||
if (hasPermission && !permission.getVerbs().isEmpty())
|
||||
{
|
||||
String perm = permission.getType().getPermissionPrefix().concat(repository.getId());
|
||||
String perm = "repository:" + String.join(",", permission.getVerbs()) + ":" + repository.getId();
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("add repository permission {} for user {} at repository {}",
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
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.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.unmodifiableCollection;
|
||||
|
||||
public class RepositoryPermissionProvider {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RepositoryPermissionProvider.class);
|
||||
private static final String REPOSITORY_PERMISSION_DESCRIPTOR = "META-INF/scm/repository-permissions.xml";
|
||||
private final Collection<String> availableVerbs;
|
||||
private final Collection<RepositoryRole> availableRoles;
|
||||
|
||||
@Inject
|
||||
public RepositoryPermissionProvider(PluginLoader pluginLoader) {
|
||||
AvailableRepositoryPermissions availablePermissions = readAvailablePermissions(pluginLoader);
|
||||
this.availableVerbs = unmodifiableCollection(new LinkedHashSet<>(availablePermissions.availableVerbs));
|
||||
this.availableRoles = unmodifiableCollection(new LinkedHashSet<>(availablePermissions.availableRoles.stream().map(r -> new RepositoryRole(r.name, r.verbs.verbs)).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
public Collection<String> availableVerbs() {
|
||||
return availableVerbs;
|
||||
}
|
||||
|
||||
public Collection<RepositoryRole> availableRoles() {
|
||||
return availableRoles;
|
||||
}
|
||||
|
||||
private static AvailableRepositoryPermissions readAvailablePermissions(PluginLoader pluginLoader) {
|
||||
Collection<String> availableVerbs = new ArrayList<>();
|
||||
Collection<RoleDescriptor> availableRoles = new ArrayList<>();
|
||||
|
||||
try {
|
||||
JAXBContext context =
|
||||
JAXBContext.newInstance(RepositoryPermissionsRoot.class);
|
||||
|
||||
// Querying permissions from uberClassLoader returns also the permissions from plugin
|
||||
Enumeration<URL> descriptorEnum =
|
||||
pluginLoader.getUberClassLoader().getResources(REPOSITORY_PERMISSION_DESCRIPTOR);
|
||||
|
||||
while (descriptorEnum.hasMoreElements()) {
|
||||
URL descriptorUrl = descriptorEnum.nextElement();
|
||||
|
||||
logger.debug("read repository permission descriptor from {}", descriptorUrl);
|
||||
|
||||
RepositoryPermissionsRoot repositoryPermissionsRoot = parsePermissionDescriptor(context, descriptorUrl);
|
||||
availableVerbs.addAll(repositoryPermissionsRoot.verbs.verbs);
|
||||
availableRoles.addAll(repositoryPermissionsRoot.roles.roles);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.error("could not read permission descriptors", ex);
|
||||
} catch (JAXBException ex) {
|
||||
logger.error(
|
||||
"could not create jaxb context to read permission descriptors", ex);
|
||||
}
|
||||
|
||||
return new AvailableRepositoryPermissions(availableVerbs, availableRoles);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static RepositoryPermissionsRoot parsePermissionDescriptor(JAXBContext context, URL descriptorUrl) {
|
||||
try {
|
||||
RepositoryPermissionsRoot descriptorWrapper =
|
||||
(RepositoryPermissionsRoot) context.createUnmarshaller().unmarshal(
|
||||
descriptorUrl);
|
||||
logger.trace("repository permissions from {}: {}", descriptorUrl, descriptorWrapper.verbs.verbs);
|
||||
logger.trace("repository roles from {}: {}", descriptorUrl, descriptorWrapper.roles.roles);
|
||||
return descriptorWrapper;
|
||||
} catch (JAXBException ex) {
|
||||
logger.error("could not parse permission descriptor", ex);
|
||||
return new RepositoryPermissionsRoot();
|
||||
}
|
||||
}
|
||||
|
||||
private static class AvailableRepositoryPermissions {
|
||||
private final Collection<String> availableVerbs;
|
||||
private final Collection<RoleDescriptor> availableRoles;
|
||||
|
||||
private AvailableRepositoryPermissions(Collection<String> availableVerbs, Collection<RoleDescriptor> availableRoles) {
|
||||
this.availableVerbs = unmodifiableCollection(availableVerbs);
|
||||
this.availableRoles = unmodifiableCollection(availableRoles);
|
||||
}
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "repository-permissions")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
private static class RepositoryPermissionsRoot {
|
||||
private VerbListDescriptor verbs = new VerbListDescriptor();
|
||||
private RoleListDescriptor roles = new RoleListDescriptor();
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "verbs")
|
||||
private static class VerbListDescriptor {
|
||||
@XmlElement(name = "verb")
|
||||
private List<String> verbs = new ArrayList<>();
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "roles")
|
||||
private static class RoleListDescriptor {
|
||||
@XmlElement(name = "role")
|
||||
private List<RoleDescriptor> roles = new ArrayList<>();
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "role")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class RoleDescriptor {
|
||||
@XmlElement(name = "name")
|
||||
private String name;
|
||||
@XmlElement(name = "verbs")
|
||||
private VerbListDescriptor verbs = new VerbListDescriptor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package sonia.scm.security;
|
||||
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
public class RepositoryRole {
|
||||
|
||||
private final String name;
|
||||
private final Collection<String> verbs;
|
||||
|
||||
public RepositoryRole(String name, Collection<String> verbs) {
|
||||
this.name = name;
|
||||
this.verbs = verbs;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Collection<String> getVerbs() {
|
||||
return Collections.unmodifiableCollection(verbs);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Role " + name + " (" + String.join(", ", verbs) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof RepositoryRole)) return false;
|
||||
RepositoryRole that = (RepositoryRole) o;
|
||||
return name.equals(that.name) &&
|
||||
CollectionUtils.isEqualCollection(this.verbs, that.verbs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, verbs.size());
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ package sonia.scm.web.cgi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
@@ -139,12 +140,6 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
||||
apendOsEnvironment(env);
|
||||
}
|
||||
|
||||
// workaround for mercurial 2.1
|
||||
if (isContentLengthWorkaround())
|
||||
{
|
||||
env.set(ENV_CONTENT_LENGTH, Integer.toString(request.getContentLength()));
|
||||
}
|
||||
|
||||
if (workDirectory == null)
|
||||
{
|
||||
workDirectory = command.getParentFile();
|
||||
@@ -304,26 +299,10 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
||||
String uri = HttpUtil.removeMatrixParameter(request.getRequestURI());
|
||||
String scriptName = uri.substring(0, uri.length() - pathInfo.length());
|
||||
String scriptPath = context.getRealPath(scriptName);
|
||||
int len = request.getContentLength();
|
||||
EnvList env = new EnvList();
|
||||
|
||||
env.set(ENV_AUTH_TYPE, request.getAuthType());
|
||||
|
||||
/**
|
||||
* Note CGI spec says CONTENT_LENGTH must be NULL ("") or undefined
|
||||
* if there is no content, so we cannot put 0 or -1 in as per the
|
||||
* Servlet API spec.
|
||||
*
|
||||
* see org.apache.catalina.servlets.CGIServlet
|
||||
*/
|
||||
if (len <= 0)
|
||||
{
|
||||
env.set(ENV_CONTENT_LENGTH, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
env.set(ENV_CONTENT_LENGTH, Integer.toString(len));
|
||||
}
|
||||
env.set(ENV_CONTENT_LENGTH, createCGIContentLength(request, contentLengthWorkaround));
|
||||
|
||||
/**
|
||||
* Decode PATH_INFO
|
||||
@@ -383,6 +362,39 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content length as string in the cgi specific format.
|
||||
*
|
||||
* CGI spec says CONTENT_LENGTH must be NULL ("") or undefined
|
||||
* if there is no content, so we cannot put 0 or -1 in as per the
|
||||
* Servlet API spec. Some CGI applications require a content
|
||||
* length environment variable, which is not null or empty
|
||||
* (e.g. mercurial). For those application the disallowEmptyResults
|
||||
* parameter should be used.
|
||||
*
|
||||
* @param disallowEmptyResults {@code true} to return -1 instead of an empty string
|
||||
*
|
||||
* @return content length as cgi specific string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String createCGIContentLength(HttpServletRequest request, boolean disallowEmptyResults) {
|
||||
String cgiContentLength = disallowEmptyResults ? "-1" : "";
|
||||
|
||||
String contentLength = request.getHeader("Content-Length");
|
||||
if (!Strings.isNullOrEmpty(contentLength)) {
|
||||
try {
|
||||
long len = Long.parseLong(contentLength);
|
||||
if (len > 0) {
|
||||
cgiContentLength = String.valueOf(len);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
logger.warn("received request with invalid content-length header value: {}", contentLength);
|
||||
}
|
||||
}
|
||||
|
||||
return cgiContentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -78,8 +78,9 @@ public class I18nServlet extends HttpServlet {
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse response) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
try (PrintWriter out = response.getWriter()) {
|
||||
response.setContentType("application/json");
|
||||
String path = req.getServletPath();
|
||||
Function<String, Optional<JsonNode>> jsonFileProvider = usedPath -> Optional.empty();
|
||||
BiConsumer<String, JsonNode> createdJsonFileConsumer = (usedPath, jsonNode) -> log.debug("A json File is created from the path {}", usedPath);
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.PushStateDispatcher;
|
||||
import sonia.scm.filter.WebElement;
|
||||
@@ -74,6 +75,9 @@ public class HttpProtocolServlet extends HttpServlet {
|
||||
} catch (NotFoundException e) {
|
||||
log.debug(e.getMessage());
|
||||
resp.setStatus(HttpStatus.SC_NOT_FOUND);
|
||||
} catch (AuthorizationException e) {
|
||||
log.debug(e.getMessage());
|
||||
resp.setStatus(HttpStatus.SC_FORBIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user