merge repository heads

This commit is contained in:
Sebastian Sdorra
2019-01-29 16:01:36 +01:00
61 changed files with 1715 additions and 1399 deletions

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -56,6 +56,7 @@ 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()));
}

View File

@@ -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(ChangesetToChangesetDtoMapper.class).getClass());

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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();
}
}
}

View File

@@ -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 {}",

View File

@@ -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();
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}
}