From decc6d29d83937edd30d565f45e689738cdad8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 22 Jan 2019 10:18:17 +0100 Subject: [PATCH 01/48] Rename Permission -> RepositoryPermission --- .../java/sonia/scm/api/v2/resources/MapperModule.java | 2 +- ...toryPermissionDtoToRepositoryPermissionMapper.java} | 2 +- ...urce.java => RepositoryPermissionRootResource.java} | 6 +++--- .../sonia/scm/api/v2/resources/RepositoryResource.java | 6 +++--- .../java/sonia/scm/api/v2/resources/ResourceLinks.java | 2 +- ....java => RepositoryPermissionRootResourceTest.java} | 10 +++++----- .../sonia/scm/api/v2/resources/RepositoryTestBase.java | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) rename scm-webapp/src/main/java/sonia/scm/api/v2/resources/{PermissionDtoToPermissionMapper.java => RepositoryPermissionDtoToRepositoryPermissionMapper.java} (88%) rename scm-webapp/src/main/java/sonia/scm/api/v2/resources/{PermissionRootResource.java => RepositoryPermissionRootResource.java} (96%) rename scm-webapp/src/test/java/sonia/scm/api/v2/resources/{PermissionRootResourceTest.java => RepositoryPermissionRootResourceTest.java} (97%) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 4cf66f7b28..859a6481f6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -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()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionDtoToPermissionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDtoToRepositoryPermissionMapper.java similarity index 88% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionDtoToPermissionMapper.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDtoToRepositoryPermissionMapper.java index 8d9761c28c..43efb19c07 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionDtoToPermissionMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDtoToRepositoryPermissionMapper.java @@ -5,7 +5,7 @@ import org.mapstruct.MappingTarget; import sonia.scm.repository.RepositoryPermission; @Mapper -public abstract class PermissionDtoToPermissionMapper { +public abstract class RepositoryPermissionDtoToRepositoryPermissionMapper { public abstract RepositoryPermission map(RepositoryPermissionDto permissionDto); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java similarity index 96% rename from scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java rename to scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java index cb3821cd67..18db82b91a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java @@ -35,10 +35,10 @@ 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; @@ -46,7 +46,7 @@ public class PermissionRootResource { @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; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index b884a37771..5cc9df38f8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -39,7 +39,7 @@ public class RepositoryResource { private final Provider changesetRootResource; private final Provider sourceRootResource; private final Provider contentResource; - private final Provider permissionRootResource; + private final Provider permissionRootResource; private final Provider diffRootResource; private final Provider modificationsRootResource; private final Provider fileHistoryRootResource; @@ -54,7 +54,7 @@ public class RepositoryResource { Provider branchRootResource, Provider changesetRootResource, Provider sourceRootResource, Provider contentResource, - Provider permissionRootResource, + Provider permissionRootResource, Provider diffRootResource, Provider modificationsRootResource, Provider fileHistoryRootResource, @@ -194,7 +194,7 @@ public class RepositoryResource { } @Path("permissions/") - public PermissionRootResource permissions() { + public RepositoryPermissionRootResource permissions() { return permissionRootResource.get(); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index be036007be..c7369f7cd0 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -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) { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java similarity index 97% rename from scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java rename to scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index 012656c4cd..d1843ef14d 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/PermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -66,7 +66,7 @@ import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX; password = "secret", configuration = "classpath:sonia/scm/repository/shiro.ini" ) -public class PermissionRootResourceTest extends RepositoryTestBase { +public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { private static final String REPOSITORY_NAMESPACE = "repo_namespace"; private static final String REPOSITORY_NAME = "repo"; private static final String PERMISSION_WRITE = "repository:permissionWrite:" + REPOSITORY_NAME; @@ -124,11 +124,11 @@ public class PermissionRootResourceTest extends RepositoryTestBase { private RepositoryPermissionToRepositoryPermissionDtoMapperImpl permissionToPermissionDtoMapper; @InjectMocks - private PermissionDtoToPermissionMapperImpl permissionDtoToPermissionMapper; + private RepositoryPermissionDtoToRepositoryPermissionMapperImpl permissionDtoToPermissionMapper; private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper; - private PermissionRootResource permissionRootResource; + private RepositoryPermissionRootResource repositoryPermissionRootResource; private final Subject subject = mock(Subject.class); private final ThreadState subjectThreadState = new SubjectThreadState(subject); @@ -138,8 +138,8 @@ public class PermissionRootResourceTest extends RepositoryTestBase { public void prepareEnvironment() { initMocks(this); repositoryPermissionCollectionToDtoMapper = new RepositoryPermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks); - permissionRootResource = new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, repositoryPermissionCollectionToDtoMapper, resourceLinks, repositoryManager); - super.permissionRootResource = Providers.of(permissionRootResource); + repositoryPermissionRootResource = new RepositoryPermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, repositoryPermissionCollectionToDtoMapper, resourceLinks, repositoryManager); + super.permissionRootResource = Providers.of(repositoryPermissionRootResource); dispatcher = createDispatcher(getRepositoryRootResource()); subjectThreadState.bind(); ThreadContext.bind(subject); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java index b2daf0536c..a8901e2d79 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryTestBase.java @@ -16,7 +16,7 @@ public abstract class RepositoryTestBase { protected Provider changesetRootResource; protected Provider sourceRootResource; protected Provider contentResource; - protected Provider permissionRootResource; + protected Provider permissionRootResource; protected Provider diffRootResource; protected Provider modificationsRootResource; protected Provider fileHistoryRootResource; From 4dcbcb80e7b125462ad0554b5bd6efb48bd795af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 22 Jan 2019 13:00:02 +0100 Subject: [PATCH 02/48] Remove permissions from repository --- .../java/sonia/scm/repository/Repository.java | 32 +------- .../RepositoryCollectionResource.java | 3 +- .../RepositoryDtoToRepositoryMapper.java | 1 - ...sitoryPermissionCollectionToDtoMapper.java | 11 +-- .../RepositoryPermissionRootResource.java | 50 +++++++------ .../api/v2/resources/RepositoryResource.java | 1 - .../AuthorizationChangedEventProducer.java | 4 +- .../DefaultAuthorizationCollector.java | 7 +- .../RepositoryPermissionAssigner.java | 6 ++ .../RepositoryPermissionRootResourceTest.java | 3 +- .../resources/RepositoryRootResourceTest.java | 35 ++------- .../RepositoryToRepositoryDtoMapperTest.java | 1 - .../DefaultRepositoryManagerPerfTest.java | 2 +- ...AuthorizationChangedEventProducerTest.java | 73 ++++++++++--------- .../DefaultAuthorizationCollectorTest.java | 5 +- 15 files changed, 103 insertions(+), 131 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 622eed6ad6..310a0f0556 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -68,7 +68,6 @@ import java.util.Set; @XmlRootElement(name = "repositories") public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject{ - private static final long serialVersionUID = 3486560714961909711L; private String contact; @@ -81,7 +80,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per private Long lastModified; private String namespace; private String name; - private final Set permissions = new HashSet<>(); @XmlElement(name = "public") private boolean publicReadable = false; private boolean archived = false; @@ -119,20 +117,14 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per * @param contact email address of a person who is responsible for * this repository. * @param description a short description of the repository - * @param permissions permissions for specific users and groups. */ - public Repository(String id, String type, String namespace, String name, String contact, - String description, RepositoryPermission... permissions) { + public Repository(String id, String type, String namespace, String name, String contact, String description) { this.id = id; this.type = type; this.namespace = namespace; this.name = name; this.contact = contact; this.description = description; - - if (Util.isNotEmpty(permissions)) { - this.permissions.addAll(Arrays.asList(permissions)); - } } /** @@ -201,10 +193,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return new NamespaceAndName(getNamespace(), getName()); } - public Collection getPermissions() { - return Collections.unmodifiableCollection(permissions); - } - /** * Returns the type (hg, git, svn ...) of the {@link Repository}. * @@ -297,19 +285,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per this.name = name; } - public void setPermissions(Collection permissions) { - this.permissions.clear(); - this.permissions.addAll(permissions); - } - - public void addPermission(RepositoryPermission newPermission) { - this.permissions.add(newPermission); - } - - public void removePermission(RepositoryPermission permission) { - this.permissions.remove(permission); - } - public void setPublicReadable(boolean publicReadable) { this.publicReadable = publicReadable; } @@ -347,7 +322,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per repository.setCreationDate(creationDate); repository.setLastModified(lastModified); repository.setDescription(description); - repository.setPermissions(permissions); repository.setPublicReadable(publicReadable); repository.setArchived(archived); @@ -379,7 +353,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per && Objects.equal(description, other.description) && Objects.equal(publicReadable, other.publicReadable) && Objects.equal(archived, other.archived) - && Objects.equal(permissions, other.permissions) && Objects.equal(type, other.type) && Objects.equal(creationDate, other.creationDate) && Objects.equal(lastModified, other.lastModified) @@ -390,7 +363,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per @Override public int hashCode() { return Objects.hashCode(id, namespace, name, contact, description, publicReadable, - archived, permissions, type, creationDate, lastModified, properties, + archived, type, creationDate, lastModified, properties, healthCheckFailures); } @@ -404,7 +377,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per .add("description", description) .add("publicReadable", publicReadable) .add("archived", archived) - .add("permissions", permissions) .add("type", type) .add("lastModified", lastModified) .add("creationDate", creationDate) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java index 420e08fe96..e53db7ba13 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java @@ -100,7 +100,8 @@ public class RepositoryCollectionResource { private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) { Repository repository = dtoToRepositoryMapper.map(repositoryDto, null); - repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), PermissionType.OWNER))); + // TODO RP +// repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), PermissionType.OWNER))); return repository; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java index b7058d2830..b9add14529 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java @@ -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 diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java index 5e678212e8..9c05f80de4 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java @@ -26,11 +26,12 @@ public class RepositoryPermissionCollectionToDtoMapper { } public HalRepresentation map(Repository repository) { - List repositoryPermissionDtoList = repository.getPermissions() - .stream() - .map(permission -> repositoryPermissionToRepositoryPermissionDtoMapper.map(permission, repository)) - .collect(toList()); - return new HalRepresentation(createLinks(repository), embedDtos(repositoryPermissionDtoList)); +// List repositoryPermissionDtoList = repository.getPermissions() +// .stream() +// .map(permission -> repositoryPermissionToRepositoryPermissionDtoMapper.map(permission, repository)) +// .collect(toList()); +// return new HalRepresentation(createLinks(repository), embedDtos(repositoryPermissionDtoList)); + return new HalRepresentation(createLinks(repository)); } private Links createLinks(Repository repository) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java index 18db82b91a..9c22a5e1b0 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java @@ -78,7 +78,8 @@ public class RepositoryPermissionRootResource { Repository repository = load(namespace, name); RepositoryPermissions.permissionWrite(repository).check(); checkPermissionAlreadyExists(permission, repository); - repository.addPermission(dtoToModelMapper.map(permission)); + // TODO RP +// repository.addPermission(dtoToModelMapper.map(permission)); manager.modify(repository); String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission); return Response.created(URI.create(resourceLinks.repositoryPermission().self(namespace, name, urlPermissionName))).build(); @@ -106,12 +107,13 @@ public class RepositoryPermissionRootResource { Repository repository = load(namespace, name); RepositoryPermissions.permissionRead(repository).check(); return Response.ok( - repository.getPermissions() - .stream() - .filter(filterPermission(permissionName)) - .map(permission -> modelToDtoMapper.map(permission, repository)) - .findFirst() - .orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name))) + // TODO RP +// repository.getPermissions() +// .stream() +// .filter(filterPermission(permissionName)) +// .map(permission -> modelToDtoMapper.map(permission, repository)) +// .findFirst() +// .orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name))) ).build(); } @@ -172,12 +174,14 @@ public class RepositoryPermissionRootResource { if (!extractedPermissionName.equals(permission.getName())) { checkPermissionAlreadyExists(permission, repository); } - RepositoryPermission existingPermission = repository.getPermissions() - .stream() - .filter(filterPermission(permissionName)) - .findFirst() - .orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name))); - dtoToModelMapper.modify(existingPermission, permission); + + // TODO RP +// RepositoryPermission existingPermission = repository.getPermissions() +// .stream() +// .filter(filterPermission(permissionName)) +// .findFirst() +// .orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name))); +// dtoToModelMapper.modify(existingPermission, permission); manager.modify(repository); log.info("the permission with name: {} is updated.", permissionName); return Response.noContent().build(); @@ -204,12 +208,13 @@ public class RepositoryPermissionRootResource { log.info("try to delete the permission with name: {}.", permissionName); Repository repository = load(namespace, name); RepositoryPermissions.modify(repository).check(); - repository.getPermissions() - .stream() - .filter(filterPermission(permissionName)) - .findFirst() - .ifPresent(repository::removePermission) - ; + // TODO RP +// repository.getPermissions() +// .stream() +// .filter(filterPermission(permissionName)) +// .findFirst() +// .ifPresent(repository::removePermission) +// ; manager.modify(repository); log.info("the permission with name: {} is updated.", permissionName); return Response.noContent().build(); @@ -261,9 +266,10 @@ public class RepositoryPermissionRootResource { } private boolean isPermissionExist(RepositoryPermissionDto permission, Repository repository) { - return repository.getPermissions() - .stream() - .anyMatch(p -> p.getName().equals(permission.getName()) && p.isGroupPermission() == permission.isGroupPermission()); + return true; +// return repository.getPermissions() +// .stream() +// .anyMatch(p -> p.getName().equals(permission.getName()) && p.isGroupPermission() == permission.isGroupPermission()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index 5cc9df38f8..e8c303e0f8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -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; } diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java index 0586db2bb3..c3e7dc4f0c 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java @@ -167,7 +167,9 @@ public class AuthorizationChangedEventProducer { private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) { return repository.isArchived() != beforeModification.isArchived() || repository.isPublicReadable() != beforeModification.isPublicReadable() - || !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions())); + // TODO RP +// || !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions())) + ; } private void fireEventForEveryUser() { diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java index a5d99c2928..76dc036e25 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java @@ -63,6 +63,7 @@ import sonia.scm.user.UserPermissions; import sonia.scm.util.Util; import java.util.Collection; +import java.util.Collections; import java.util.Set; //~--- JDK imports ------------------------------------------------------------ @@ -198,8 +199,12 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector private void collectRepositoryPermissions(Builder builder, Repository repository, User user, GroupNames groups) { + + // TODO RP + Collection repositoryPermissions - = repository.getPermissions(); + = Collections.emptyList(); +// = repository.getPermissions(); if (Util.isNotEmpty(repositoryPermissions)) { diff --git a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java new file mode 100644 index 0000000000..5d0da1ae1c --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java @@ -0,0 +1,6 @@ +package sonia.scm.security; + +public class RepositoryPermissionAssigner { + + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index d1843ef14d..48ff451298 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -412,7 +412,8 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { } private void createUserWithRepositoryAndPermissions(ArrayList permissions, String userPermission) { - createUserWithRepository(userPermission).setPermissions(permissions); + // TODO RP +// createUserWithRepository(userPermission).setPermissions(permissions); } private Stream createDynamicTestsToAssertResponses(ExpectedRequest... expectedRequests) { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index 1677be95b1..bdb58fb73b 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -291,34 +291,13 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { dispatcher.invoke(request, response); - Assertions.assertThat(createCaptor.getValue().getPermissions()) - .hasSize(1) - .allSatisfy(p -> { - assertThat(p.getName()).isEqualTo("trillian"); - assertThat(p.getType()).isEqualTo(PermissionType.OWNER); - }); - } - - @Test - public void shouldNotOverwriteExistingPermissionsOnUpdate() throws Exception { - Repository existingRepository = mockRepository("space", "repo"); - existingRepository.setPermissions(singletonList(new RepositoryPermission("user", PermissionType.READ))); - - URL url = Resources.getResource("sonia/scm/api/v2/repository-test-update.json"); - byte[] repository = Resources.toByteArray(url); - - ArgumentCaptor modifiedRepositoryCaptor = forClass(Repository.class); - doNothing().when(repositoryManager).modify(modifiedRepositoryCaptor.capture()); - - MockHttpRequest request = MockHttpRequest - .put("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo") - .contentType(VndMediaType.REPOSITORY) - .content(repository); - MockHttpResponse response = new MockHttpResponse(); - - dispatcher.invoke(request, response); - - assertFalse(modifiedRepositoryCaptor.getValue().getPermissions().isEmpty()); + // TODO RP +// Assertions.assertThat(createCaptor.getValue().getPermissions()) +// .hasSize(1) +// .allSatisfy(p -> { +// assertThat(p.getName()).isEqualTo("trillian"); +// assertThat(p.getType()).isEqualTo(PermissionType.OWNER); +// }); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java index 9bf70093fd..c9e33d6727 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java @@ -238,7 +238,6 @@ public class RepositoryToRepositoryDtoMapperTest { repository.setId("1"); repository.setCreationDate(System.currentTimeMillis()); repository.setHealthCheckFailures(singletonList(new HealthCheckFailure("1", "summary", "url", "failure"))); - repository.setPermissions(singletonList(new RepositoryPermission("permission", PermissionType.READ))); return repository; } diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java index 146810e787..8c643d59a2 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java @@ -184,7 +184,7 @@ private long calculateAverage(List times) { private Repository createTestRepository(int number) { Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number); - repository.addPermission(new RepositoryPermission("trillian", PermissionType.READ)); +// repository.addPermission(new RepositoryPermission("trillian", PermissionType.READ)); return repository; } diff --git a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java index ab8ce5dce8..abc0b33626 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java @@ -172,42 +172,43 @@ public class AuthorizationChangedEventProducerTest { @Test public void testOnRepositoryModificationEvent() { - Repository repositoryModified = RepositoryTestData.createHeartOfGold(); - repositoryModified.setName("test123"); - repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test"))); - - Repository repository = RepositoryTestData.createHeartOfGold(); - repository.setPermissions(Lists.newArrayList(new RepositoryPermission("test"))); - - producer.onEvent(new RepositoryModificationEvent(HandlerEventType.BEFORE_CREATE, repositoryModified, repository)); - assertEventIsNotFired(); - - producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); - assertEventIsNotFired(); - - repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test"))); - producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); - assertEventIsNotFired(); - - repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test123"))); - producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); - assertGlobalEventIsFired(); - - resetStoredEvent(); - - repositoryModified.setPermissions( - Lists.newArrayList(new RepositoryPermission("test", PermissionType.READ, true)) - ); - producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); - assertGlobalEventIsFired(); - - resetStoredEvent(); - - repositoryModified.setPermissions( - Lists.newArrayList(new RepositoryPermission("test", PermissionType.WRITE)) - ); - producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); - assertGlobalEventIsFired(); + // TODO RP +// Repository repositoryModified = RepositoryTestData.createHeartOfGold(); +// repositoryModified.setName("test123"); +// repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test"))); +// +// Repository repository = RepositoryTestData.createHeartOfGold(); +// repository.setPermissions(Lists.newArrayList(new RepositoryPermission("test"))); +// +// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.BEFORE_CREATE, repositoryModified, repository)); +// assertEventIsNotFired(); +// +// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); +// assertEventIsNotFired(); +// +// repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test"))); +// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); +// assertEventIsNotFired(); +// +// repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test123"))); +// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); +// assertGlobalEventIsFired(); +// +// resetStoredEvent(); +// +// repositoryModified.setPermissions( +// Lists.newArrayList(new RepositoryPermission("test", PermissionType.READ, true)) +// ); +// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); +// assertGlobalEventIsFired(); +// +// resetStoredEvent(); +// +// repositoryModified.setPermissions( +// Lists.newArrayList(new RepositoryPermission("test", PermissionType.WRITE)) +// ); +// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); +// assertGlobalEventIsFired(); } private void resetStoredEvent(){ diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java index 3b3a28861f..2a7a0fa328 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java @@ -225,11 +225,12 @@ public class DefaultAuthorizationCollectorTest { authenticate(UserTestData.createTrillian(), group); Repository heartOfGold = RepositoryTestData.createHeartOfGold(); heartOfGold.setId("one"); - heartOfGold.setPermissions(Lists.newArrayList(new RepositoryPermission("trillian"))); + // TODO RP +// heartOfGold.setPermissions(Lists.newArrayList(new RepositoryPermission("trillian"))); Repository puzzle42 = RepositoryTestData.create42Puzzle(); puzzle42.setId("two"); RepositoryPermission permission = new RepositoryPermission(group, PermissionType.WRITE, true); - puzzle42.setPermissions(Lists.newArrayList(permission)); +// puzzle42.setPermissions(Lists.newArrayList(permission)); when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42)); // execute and assert From 707d63426b2e19d22bdc78073b815642b9f98184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 22 Jan 2019 13:28:52 +0100 Subject: [PATCH 03/48] Remove enum PermissionType --- .../sonia/scm/repository/PermissionType.java | 99 -------- .../scm/repository/RepositoryPermission.java | 66 ++--- .../api/IncomingCommandBuilder.java | 6 +- .../api/OutgoingCommandBuilder.java | 6 +- .../repository/api/PullCommandBuilder.java | 15 +- .../repository/api/PushCommandBuilder.java | 7 +- .../scm/security/RepositoryPermission.java | 230 ------------------ .../security/RepositoryPermissionTest.java | 87 ------- .../java/sonia/scm/it/PermissionsITCase.java | 14 +- .../java/sonia/scm/it/utils/TestData.java | 34 +-- .../java/sonia/scm/api/rest/Permission.java | 168 ------------- .../RepositoryCollectionResource.java | 4 - .../DefaultAuthorizationCollector.java | 2 +- .../RepositoryPermissionRootResourceTest.java | 19 +- ...onToRepositoryPermissionDtoMapperTest.java | 5 +- .../resources/RepositoryRootResourceTest.java | 5 - .../RepositoryToRepositoryDtoMapperTest.java | 2 - .../test/java/sonia/scm/it/GitLfsITCase.java | 17 +- ...AuthorizationChangedEventProducerTest.java | 4 - .../DefaultAuthorizationCollectorTest.java | 3 +- 20 files changed, 71 insertions(+), 722 deletions(-) delete mode 100644 scm-core/src/main/java/sonia/scm/repository/PermissionType.java delete mode 100644 scm-core/src/main/java/sonia/scm/security/RepositoryPermission.java delete mode 100644 scm-core/src/test/java/sonia/scm/security/RepositoryPermissionTest.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/Permission.java diff --git a/scm-core/src/main/java/sonia/scm/repository/PermissionType.java b/scm-core/src/main/java/sonia/scm/repository/PermissionType.java deleted file mode 100644 index bba0d44f3d..0000000000 --- a/scm-core/src/main/java/sonia/scm/repository/PermissionType.java +++ /dev/null @@ -1,99 +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.repository; - -/** - * Type of permissionPrefix for a {@link Repository}. - * - * @author Sebastian Sdorra - */ -public enum PermissionType -{ - - /** read permision */ - READ(0, "repository:read,pull:"), - - /** read and write permissionPrefix */ - WRITE(10, "repository:read,pull,push:"), - - /** - * read, write and - * also the ability to manage the properties and permissions - */ - OWNER(100, "repository:*:"); - - /** - * Constructs a new permissionPrefix type - * - * - * @param value - */ - private PermissionType(int value, String permissionPrefix) - { - this.value = value; - this.permissionPrefix = permissionPrefix; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * - * @return - * - * @since 2.0.0 - */ - public String getPermissionPrefix() - { - return permissionPrefix; - } - - /** - * Returns the integer representation of the {@link PermissionType} - * - * - * @return integer representation - */ - public int getValue() - { - return value; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final String permissionPrefix; - - /** Field description */ - private final int value; -} diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java index 0aff771fce..492142674d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java @@ -60,54 +60,18 @@ public class RepositoryPermission implements PermissionObject, Serializable private boolean groupPermission = false; private String name; - private PermissionType type = PermissionType.READ; + private String verb; /** * Constructs a new {@link RepositoryPermission}. - * This constructor is used by JAXB. - * + * This constructor is used by JAXB and mapstruct. */ public RepositoryPermission() {} - /** - * Constructs a new {@link RepositoryPermission} with type = {@link PermissionType#READ} - * for the specified user. - * - * - * @param name name of the user - */ - public RepositoryPermission(String name) + public RepositoryPermission(String name, String verb, boolean groupPermission) { - this(); this.name = name; - } - - /** - * Constructs a new {@link RepositoryPermission} with the specified type for - * the given user. - * - * - * @param name name of the user - * @param type type of the permission - */ - public RepositoryPermission(String name, PermissionType type) - { - this(name); - this.type = type; - } - - /** - * Constructs a new {@link RepositoryPermission} with the specified type for - * the given user or group. - * - * - * @param name name of the user or group - * @param type type of the permission - * @param groupPermission true if the permission is a permission for a group - */ - public RepositoryPermission(String name, PermissionType type, boolean groupPermission) - { - this(name, type); + this.verb = verb; this.groupPermission = groupPermission; } @@ -137,7 +101,7 @@ public class RepositoryPermission implements PermissionObject, Serializable final RepositoryPermission other = (RepositoryPermission) obj; return Objects.equal(name, other.name) - && Objects.equal(type, other.type) + && Objects.equal(verb, other.verb) && Objects.equal(groupPermission, other.groupPermission); } @@ -150,7 +114,7 @@ public class RepositoryPermission implements PermissionObject, Serializable @Override public int hashCode() { - return Objects.hashCode(name, type, groupPermission); + return Objects.hashCode(name, verb, groupPermission); } @@ -160,7 +124,7 @@ public class RepositoryPermission implements PermissionObject, Serializable //J- return MoreObjects.toStringHelper(this) .add("name", name) - .add("type", type) + .add("verb", verb) .add("groupPermission", groupPermission) .toString(); //J+ @@ -181,14 +145,14 @@ public class RepositoryPermission implements PermissionObject, Serializable } /** - * Returns the {@link PermissionType} of the permission. + * Returns the verb of the permission. * * - * @return {@link PermissionType} of the permission + * @return verb of the permission */ - public PermissionType getType() + public String getVerb() { - return type; + return verb; } /** @@ -228,13 +192,13 @@ public class RepositoryPermission implements PermissionObject, Serializable } /** - * Sets the type of the permission. + * Sets the verb of the permission. * * - * @param type type of the permission + * @param verb verb of the permission */ - public void setType(PermissionType type) + public void setVerb(String verb) { - this.type = type; + this.verb = verb; } } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/IncomingCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/IncomingCommandBuilder.java index 6c7c620fa4..6098bdf92b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/IncomingCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/IncomingCommandBuilder.java @@ -39,12 +39,11 @@ import org.apache.shiro.subject.Subject; import sonia.scm.cache.CacheManager; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.IncomingCommand; import sonia.scm.repository.spi.IncomingCommandRequest; -import sonia.scm.security.RepositoryPermission; import java.io.IOException; @@ -94,8 +93,7 @@ public final class IncomingCommandBuilder { Subject subject = SecurityUtils.getSubject(); - subject.checkPermission(new RepositoryPermission(remoteRepository, - PermissionType.READ)); + subject.isPermitted(RepositoryPermissions.pull(remoteRepository).asShiroString()); request.setRemoteRepository(remoteRepository); diff --git a/scm-core/src/main/java/sonia/scm/repository/api/OutgoingCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/OutgoingCommandBuilder.java index 2753128eac..d39c95e0e2 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/OutgoingCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/OutgoingCommandBuilder.java @@ -34,12 +34,11 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import sonia.scm.cache.CacheManager; import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.OutgoingCommand; import sonia.scm.repository.spi.OutgoingCommandRequest; -import sonia.scm.security.RepositoryPermission; import java.io.IOException; @@ -84,8 +83,7 @@ public final class OutgoingCommandBuilder { Subject subject = SecurityUtils.getSubject(); - subject.checkPermission(new RepositoryPermission(remoteRepository, - PermissionType.READ)); + subject.isPermitted(RepositoryPermissions.pull(remoteRepository).asShiroString()); request.setRemoteRepository(remoteRepository); diff --git a/scm-core/src/main/java/sonia/scm/repository/api/PullCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/PullCommandBuilder.java index a0f5ff4115..969ec6ef11 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/PullCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/PullCommandBuilder.java @@ -38,11 +38,10 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.PullCommand; import sonia.scm.repository.spi.PullCommandRequest; -import sonia.scm.security.RepositoryPermission; import java.io.IOException; import java.net.URL; @@ -96,9 +95,7 @@ public final class PullCommandBuilder public PullResponse pull(String url) throws IOException { Subject subject = SecurityUtils.getSubject(); //J- - subject.checkPermission( - new RepositoryPermission(localRepository, PermissionType.WRITE) - ); + subject.isPermitted(RepositoryPermissions.push(localRepository).asShiroString()); //J+ URL remoteUrl = new URL(url); @@ -124,12 +121,8 @@ public final class PullCommandBuilder Subject subject = SecurityUtils.getSubject(); //J- - subject.checkPermission( - new RepositoryPermission(localRepository, PermissionType.WRITE) - ); - subject.checkPermission( - new RepositoryPermission(remoteRepository, PermissionType.READ) - ); + subject.isPermitted(RepositoryPermissions.push(localRepository).asShiroString()); + subject.isPermitted(RepositoryPermissions.push(remoteRepository).asShiroString()); //J+ request.reset(); diff --git a/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java index 7b318e49ec..a734225281 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/PushCommandBuilder.java @@ -39,11 +39,10 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.PushCommand; import sonia.scm.repository.spi.PushCommandRequest; -import sonia.scm.security.RepositoryPermission; import java.io.IOException; import java.net.URL; @@ -92,9 +91,7 @@ public final class PushCommandBuilder Subject subject = SecurityUtils.getSubject(); //J- - subject.checkPermission( - new RepositoryPermission(remoteRepository, PermissionType.WRITE) - ); + subject.isPermitted(RepositoryPermissions.push(remoteRepository).asShiroString()); //J+ logger.info("push changes to repository {}", remoteRepository.getId()); diff --git a/scm-core/src/main/java/sonia/scm/security/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/security/RepositoryPermission.java deleted file mode 100644 index 1b0229d6f5..0000000000 --- a/scm-core/src/main/java/sonia/scm/security/RepositoryPermission.java +++ /dev/null @@ -1,230 +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.security; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; -import org.apache.shiro.authz.Permission; -import sonia.scm.repository.PermissionType; -import sonia.scm.repository.Repository; - -import java.io.Serializable; - -//~--- JDK imports ------------------------------------------------------------ - -/** - * This class represents the permission to a repository of a user. - * - * @author Sebastian Sdorra - * @since 1.21 - */ -public final class RepositoryPermission - implements StringablePermission, Serializable -{ - - /** - * Type string of the permission - * @since 1.31 - */ - public static final String TYPE = "repository"; - - /** Field description */ - public static final String WILDCARD = "*"; - - /** Field description */ - private static final long serialVersionUID = 3832804235417228043L; - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param repository - * @param permissionType - */ - public RepositoryPermission(Repository repository, - PermissionType permissionType) - { - this(repository.getId(), permissionType); - } - - /** - * Constructs ... - * - * - * @param repositoryId - * @param permissionType - */ - public RepositoryPermission(String repositoryId, - PermissionType permissionType) - { - this.repositoryId = repositoryId; - this.permissionType = permissionType; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param obj - * - * @return - */ - @Override - public boolean equals(Object obj) - { - if (obj == null) - { - return false; - } - - if (getClass() != obj.getClass()) - { - return false; - } - - final RepositoryPermission other = (RepositoryPermission) obj; - - return Objects.equal(repositoryId, other.repositoryId) - && Objects.equal(permissionType, other.permissionType); - } - - /** - * Method description - * - * - * @return - */ - @Override - public int hashCode() - { - return Objects.hashCode(repositoryId, permissionType); - } - - /** - * Method description - * - * - * @param p - * - * @return - */ - @Override - public boolean implies(Permission p) - { - boolean result = false; - - if (p instanceof RepositoryPermission) - { - RepositoryPermission rp = (RepositoryPermission) p; - - //J- - result = (repositoryId.equals(WILDCARD) || repositoryId.equals(rp.repositoryId)) - && (permissionType.getValue() >= rp.permissionType.getValue()); - //J+ - } - - return result; - } - - /** - * Method description - * - * - * @return - */ - @Override - public String toString() - { - //J- - return MoreObjects.toStringHelper(this) - .add("repositoryId", repositoryId) - .add("permissionType", permissionType) - .toString(); - //J+ - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - @Override - public String getAsString() - { - StringBuilder buffer = new StringBuilder(TYPE); - - buffer.append(":").append(repositoryId).append(":").append(permissionType); - - return buffer.toString(); - } - - /** - * Method description - * - * - * @return - */ - public PermissionType getPermissionType() - { - return permissionType; - } - - /** - * Method description - * - * - * @return - */ - public String getRepositoryId() - { - return repositoryId; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private PermissionType permissionType; - - /** Field description */ - private String repositoryId; -} diff --git a/scm-core/src/test/java/sonia/scm/security/RepositoryPermissionTest.java b/scm-core/src/test/java/sonia/scm/security/RepositoryPermissionTest.java deleted file mode 100644 index e8180ca24a..0000000000 --- a/scm-core/src/test/java/sonia/scm/security/RepositoryPermissionTest.java +++ /dev/null @@ -1,87 +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.security; - -//~--- non-JDK imports -------------------------------------------------------- - -import org.junit.Test; - -import sonia.scm.repository.PermissionType; - -import static org.junit.Assert.*; - -/** - * - * @author Sebastian Sdorra - */ -public class RepositoryPermissionTest -{ - - /** - * Method description - * - */ - @Test - public void testImplies() - { - RepositoryPermission p = new RepositoryPermission("asd", - PermissionType.READ); - - assertTrue(p.implies(new RepositoryPermission("asd", PermissionType.READ))); - assertFalse(p.implies(new RepositoryPermission("asd", - PermissionType.OWNER))); - assertFalse(p.implies(new RepositoryPermission("asd", - PermissionType.WRITE))); - p = new RepositoryPermission("asd", PermissionType.OWNER); - assertTrue(p.implies(new RepositoryPermission("asd", PermissionType.READ))); - assertFalse(p.implies(new RepositoryPermission("bdb", - PermissionType.READ))); - } - - /** - * Method description - * - */ - @Test - public void testImpliesWithWildcard() - { - RepositoryPermission p = new RepositoryPermission("*", - PermissionType.OWNER); - - assertTrue(p.implies(new RepositoryPermission("asd", PermissionType.READ))); - assertTrue(p.implies(new RepositoryPermission("bdb", - PermissionType.OWNER))); - assertTrue(p.implies(new RepositoryPermission("cgd", - PermissionType.WRITE))); - } -} diff --git a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java index aa91e67022..15f5e30abc 100644 --- a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java @@ -42,7 +42,6 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import sonia.scm.it.utils.RepositoryUtil; import sonia.scm.it.utils.TestData; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.client.api.RepositoryClient; import sonia.scm.repository.client.api.RepositoryClientException; import sonia.scm.web.VndMediaType; @@ -91,12 +90,13 @@ public class PermissionsITCase { public void prepareEnvironment() { TestData.createDefault(); TestData.createNotAdminUser(USER_READ, USER_PASS); - TestData.createUserPermission(USER_READ, PermissionType.READ, repositoryType); - TestData.createNotAdminUser(USER_WRITE, USER_PASS); - TestData.createUserPermission(USER_WRITE, PermissionType.WRITE, repositoryType); - TestData.createNotAdminUser(USER_OWNER, USER_PASS); - TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType); - TestData.createNotAdminUser(USER_OTHER, USER_PASS); + // TODO RP +// TestData.createUserPermission(USER_READ, PermissionType.READ, repositoryType); +// TestData.createNotAdminUser(USER_WRITE, USER_PASS); +// TestData.createUserPermission(USER_WRITE, PermissionType.WRITE, repositoryType); +// TestData.createNotAdminUser(USER_OWNER, USER_PASS); +// TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType); +// TestData.createNotAdminUser(USER_OTHER, USER_PASS); createdPermissions = asList(USER_READ, USER_WRITE, USER_OWNER); } diff --git a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java index 48605437c6..fb92628287 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java @@ -4,7 +4,6 @@ import io.restassured.response.ValidatableResponse; import org.apache.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.PermissionType; import sonia.scm.web.VndMediaType; import javax.json.Json; @@ -82,22 +81,23 @@ public class TestData { ; } - public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) { - String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType); - LOG.info("create permission with name {} and type: {} using the endpoint: {}", name, permissionType, defaultPermissionUrl); - given(VndMediaType.PERMISSION) - .when() - .content("{\n" + - "\t\"type\": \"" + permissionType.name() + "\",\n" + - "\t\"name\": \"" + name + "\",\n" + - "\t\"groupPermission\": false\n" + - "\t\n" + - "}") - .post(defaultPermissionUrl) - .then() - .statusCode(HttpStatus.SC_CREATED) - ; - } + // TODO RP +// public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) { +// String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType); +// LOG.info("create permission with name {} and type: {} using the endpoint: {}", name, permissionType, defaultPermissionUrl); +// given(VndMediaType.PERMISSION) +// .when() +// .content("{\n" + +// "\t\"type\": \"" + permissionType.name() + "\",\n" + +// "\t\"name\": \"" + name + "\",\n" + +// "\t\"groupPermission\": false\n" + +// "\t\n" + +// "}") +// .post(defaultPermissionUrl) +// .then() +// .statusCode(HttpStatus.SC_CREATED) +// ; +// } public static List getUserPermissions(String username, String password, String repositoryType) { return callUserPermissions(username, password, repositoryType, HttpStatus.SC_OK) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/Permission.java b/scm-webapp/src/main/java/sonia/scm/api/rest/Permission.java deleted file mode 100644 index ba707c19c2..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/Permission.java +++ /dev/null @@ -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; -} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java index e53db7ba13..cfcd279692 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java @@ -6,8 +6,6 @@ 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.user.User; @@ -24,8 +22,6 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; -import static java.util.Collections.singletonList; - public class RepositoryCollectionResource { private static final int DEFAULT_PAGE_SIZE = 10; diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java index 76dc036e25..e015ed90f6 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java @@ -214,7 +214,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector hasPermission = isUserPermitted(user, groups, permission); if (hasPermission) { - String perm = permission.getType().getPermissionPrefix().concat(repository.getId()); + String perm = null; // TODO RP permission.getType().getPermissionPrefix().concat(repository.getId()); if (logger.isTraceEnabled()) { logger.trace("add repository permission {} for user {} at repository {}", diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index 48ff451298..b66a274f0d 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -30,7 +30,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.RepositoryPermission; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; import sonia.scm.web.VndMediaType; @@ -79,12 +78,12 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { private static final String PERMISSION_TEST_PAYLOAD = "{ \"name\" : \"permission_name\", \"type\" : \"READ\" }"; private static final ArrayList TEST_PERMISSIONS = Lists .newArrayList( - new RepositoryPermission("user_write", PermissionType.WRITE, false), - new RepositoryPermission("user_read", PermissionType.READ, false), - new RepositoryPermission("user_owner", PermissionType.OWNER, false), - new RepositoryPermission("group_read", PermissionType.READ, true), - new RepositoryPermission("group_write", PermissionType.WRITE, true), - new RepositoryPermission("group_owner", PermissionType.OWNER, true) + new RepositoryPermission("user_write", "read,modify", false), + new RepositoryPermission("user_read", "read", false), + new RepositoryPermission("user_owner", "read,modify,delete", false), + new RepositoryPermission("group_read", "read", true), + new RepositoryPermission("group_write", "read,modify", true), + new RepositoryPermission("group_owner", "read,modify,delete", true) ); private final ExpectedRequest requestGETAllPermissions = new ExpectedRequest() .description("GET all permissions") @@ -259,7 +258,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { @Test public void shouldGetCreatedPermissions() throws URISyntaxException { createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE); - RepositoryPermission newPermission = new RepositoryPermission("new_group_perm", PermissionType.WRITE, true); + RepositoryPermission newPermission = new RepositoryPermission("new_group_perm", "read,modify", true); ArrayList permissions = Lists.newArrayList(TEST_PERMISSIONS); permissions.add(newPermission); ImmutableList expectedPermissions = ImmutableList.copyOf(permissions); @@ -288,7 +287,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE); RepositoryPermission modifiedPermission = TEST_PERMISSIONS.get(0); // modify the type to owner - modifiedPermission.setType(PermissionType.OWNER); + modifiedPermission.setVerb("read,modify,delete"); ImmutableList expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS); assertExpectedRequest(requestPUTPermission .content("{\"name\" : \"" + modifiedPermission.getName() + "\" , \"type\" : \"OWNER\" , \"groupPermission\" : false}") @@ -382,7 +381,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { RepositoryPermissionDto result = new RepositoryPermissionDto(); result.setName(permission.getName()); result.setGroupPermission(permission.isGroupPermission()); - result.setType(permission.getType().name()); + result.setType(permission.getVerb()); String permissionName = Optional.of(permission.getName()) .filter(p -> !permission.isGroupPermission()) .orElse(GROUP_PREFIX + permission.getName()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionToRepositoryPermissionDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionToRepositoryPermissionDtoMapperTest.java index a6ab00db58..09a3ab4855 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionToRepositoryPermissionDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionToRepositoryPermissionDtoMapperTest.java @@ -8,7 +8,6 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.RepositoryPermission; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; import java.net.URI; @@ -36,7 +35,7 @@ public class RepositoryPermissionToRepositoryPermissionDtoMapperTest { @SubjectAware(username = "trillian", password = "secret") public void shouldMapGroupPermissionCorrectly() { Repository repository = getDummyRepository(); - RepositoryPermission permission = new RepositoryPermission("42", PermissionType.OWNER, true); + RepositoryPermission permission = new RepositoryPermission("42", "read,modify,delete", true); RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository); @@ -48,7 +47,7 @@ public class RepositoryPermissionToRepositoryPermissionDtoMapperTest { @SubjectAware(username = "trillian", password = "secret") public void shouldMapNonGroupPermissionCorrectly() { Repository repository = getDummyRepository(); - RepositoryPermission permission = new RepositoryPermission("42", PermissionType.OWNER, false); + RepositoryPermission permission = new RepositoryPermission("42", "read,modify,delete", false); RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index bdb58fb73b..a18b63c53f 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -6,7 +6,6 @@ import com.google.common.io.Resources; import com.google.inject.util.Providers; import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.Subject; -import org.assertj.core.api.Assertions; import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpResponse; @@ -18,8 +17,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.PageResult; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryPermission; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryIsNotArchivedException; import sonia.scm.repository.RepositoryManager; @@ -45,11 +42,9 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentCaptor.forClass; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java index c9e33d6727..8469e966c8 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryToRepositoryDtoMapperTest.java @@ -10,8 +10,6 @@ import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.repository.HealthCheckFailure; -import sonia.scm.repository.RepositoryPermission; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.RepositoryService; diff --git a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java index c55a33c39a..242c9b3047 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java @@ -50,7 +50,6 @@ import sonia.scm.api.rest.ObjectMapperProvider; import sonia.scm.api.v2.resources.RepositoryDto; import sonia.scm.api.v2.resources.UserDto; import sonia.scm.api.v2.resources.UserToUserDtoMapperImpl; -import sonia.scm.repository.PermissionType; import sonia.scm.user.User; import sonia.scm.user.UserTestData; import sonia.scm.util.HttpUtil; @@ -117,10 +116,17 @@ public class GitLfsITCase { @Test public void testLfsAPIWithOwnerPermissions() throws IOException { - uploadAndDownloadAsUser(PermissionType.OWNER); + // TODO RP + uploadAndDownloadAsUser(); } - private void uploadAndDownloadAsUser(PermissionType permissionType) throws IOException { + @Test + public void testLfsAPIWithWritePermissions() throws IOException { + // TODO RP + uploadAndDownloadAsUser(); + } + + private void uploadAndDownloadAsUser() throws IOException { User trillian = UserTestData.createTrillian(); trillian.setPassword("secret123"); createUser(trillian); @@ -140,11 +146,6 @@ public class GitLfsITCase { } } - @Test - public void testLfsAPIWithWritePermissions() throws IOException { - uploadAndDownloadAsUser(PermissionType.WRITE); - } - private void createUser(User user) { UserDto dto = new UserToUserDtoMapperImpl(){ @Override diff --git a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java index abc0b33626..de31aa1298 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java @@ -31,7 +31,6 @@ package sonia.scm.security; -import com.google.common.collect.Lists; import org.junit.Test; import static org.junit.Assert.*; import org.junit.Before; @@ -39,11 +38,8 @@ import sonia.scm.HandlerEventType; import sonia.scm.group.Group; import sonia.scm.group.GroupEvent; import sonia.scm.group.GroupModificationEvent; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryEvent; -import sonia.scm.repository.RepositoryModificationEvent; -import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.RepositoryTestData; import sonia.scm.user.User; import sonia.scm.user.UserEvent; diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java index 2a7a0fa328..bfb97d0b40 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java @@ -51,7 +51,6 @@ import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.config.ScmConfiguration; import sonia.scm.group.GroupNames; -import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryDAO; import sonia.scm.repository.RepositoryPermission; @@ -229,7 +228,7 @@ public class DefaultAuthorizationCollectorTest { // heartOfGold.setPermissions(Lists.newArrayList(new RepositoryPermission("trillian"))); Repository puzzle42 = RepositoryTestData.create42Puzzle(); puzzle42.setId("two"); - RepositoryPermission permission = new RepositoryPermission(group, PermissionType.WRITE, true); + RepositoryPermission permission = new RepositoryPermission(group, "read,modify", true); // puzzle42.setPermissions(Lists.newArrayList(permission)); when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42)); From 101b21e914c2c153a4867e2acb4f42a99c5ca4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 22 Jan 2019 14:30:56 +0100 Subject: [PATCH 04/48] Read available repository permissions --- .../RepositoryPermissionAssigner.java | 130 ++++++++++++++++++ .../META-INF/scm/repository-permissions.xml | 35 +++++ .../RepositoryPermissionAssignerTest.java | 25 ++++ 3 files changed, 190 insertions(+) create mode 100644 scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml create mode 100644 scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionAssignerTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java index 5d0da1ae1c..59bc489144 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java +++ b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java @@ -1,6 +1,136 @@ package sonia.scm.security; +import com.google.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.store.ConfigurationEntryStoreFactory; + +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.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.stream.Collectors; + public class RepositoryPermissionAssigner { + private static final Logger logger = LoggerFactory.getLogger(RepositoryPermissionAssigner.class); + private static final String NAME = "permissions"; + private static final String REPOSITORY_PERMISSION_DESCRIPTOR = "META-INF/scm/repository-permissions.xml"; + private final ConfigurationEntryStoreFactory storeFactory; + private final AvailableRepositoryPermissions availablePermissions; + @Inject + public RepositoryPermissionAssigner(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader) { + this.storeFactory = storeFactory; + this.availablePermissions = readAvailablePermissions(pluginLoader); + } + + public Collection availableVerbs() { + return availablePermissions.availableVerbs; + } + + public Collection availableRoles() { + return availablePermissions.availableRoles; + } + + private static AvailableRepositoryPermissions readAvailablePermissions(PluginLoader pluginLoader) { + Collection availableVerbs = new ArrayList<>(); + Collection availableRoles = new ArrayList<>(); + + try { + JAXBContext context = + JAXBContext.newInstance(RepositoryPermissionsRoot.class); + + // Querying permissions from uberClassLoader returns also the permissions from plugin + Enumeration descriptorEnum = + pluginLoader.getUberClassLoader().getResources(REPOSITORY_PERMISSION_DESCRIPTOR); + + while (descriptorEnum.hasMoreElements()) { + URL descriptorUrl = descriptorEnum.nextElement(); + + logger.debug("read 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("permissions from {}: {}", descriptorUrl, descriptorWrapper); + return descriptorWrapper; + } catch (JAXBException ex) { + logger.error("could not parse permission descriptor", ex); + return new RepositoryPermissionsRoot(); + } + } + + private static class AvailableRepositoryPermissions { + private final Collection availableVerbs; + private final Collection availableRoles; + + private AvailableRepositoryPermissions(Collection availableVerbs, Collection availableRoles) { + this.availableVerbs = Collections.unmodifiableCollection(availableVerbs); + this.availableRoles = Collections.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 verbs = new ArrayList<>(); + } + + @XmlRootElement(name = "roles") + private static class RoleListDescriptor { + @XmlElement(name = "role") + private List 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(); + + public Collection getVerbs() { + return Collections.unmodifiableCollection(verbs.verbs); + } + + public String toString() { + return "Role " + name + " (" + verbs.verbs.stream().collect(Collectors.joining(", ")) + ")"; + } + } } diff --git a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml new file mode 100644 index 0000000000..9df450efb0 --- /dev/null +++ b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml @@ -0,0 +1,35 @@ + + + abc + xyz + + + + OWNER + + * + + + + WRITER + + read + push + pull + + + + READER + + read + pull + + + + HEALTH + + healthCheck + + + + diff --git a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionAssignerTest.java b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionAssignerTest.java new file mode 100644 index 0000000000..d037c2cbfe --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionAssignerTest.java @@ -0,0 +1,25 @@ +package sonia.scm.security; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.store.ConfigurationEntryStoreFactory; +import sonia.scm.util.ClassLoaders; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class RepositoryPermissionAssignerTest { + + @Test + void x() { + PluginLoader pluginLoader = mock(PluginLoader.class); + when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class)); + ConfigurationEntryStoreFactory configurationEntryStoreFactory = mock(ConfigurationEntryStoreFactory.class); + RepositoryPermissionAssigner repositoryPermissionAssigner = new RepositoryPermissionAssigner(configurationEntryStoreFactory, pluginLoader); + Assertions.assertThat(repositoryPermissionAssigner.availableVerbs()).isNotEmpty(); + Assertions.assertThat(repositoryPermissionAssigner.availableRoles()).isNotEmpty().noneMatch(r -> r.getVerbs().isEmpty()); + System.out.println(repositoryPermissionAssigner.availableVerbs()); + System.out.println(repositoryPermissionAssigner.availableRoles()); + } +} From 9b4fc5e3d8099854b084f02f770edf4899bf7b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 22 Jan 2019 17:22:59 +0100 Subject: [PATCH 05/48] Persist permissions in repository --- .../java/sonia/scm/repository/Repository.java | 31 +++++++++++++++++-- .../scm/repository/RepositoryPermission.java | 25 ++++++++------- .../RepositoryCollectionResource.java | 7 +++-- .../RepositoryPermissionRootResourceTest.java | 19 ++++++------ ...onToRepositoryPermissionDtoMapperTest.java | 5 +-- .../DefaultAuthorizationCollectorTest.java | 3 +- 6 files changed, 63 insertions(+), 27 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 310a0f0556..3adb215d27 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -80,6 +80,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per private Long lastModified; private String namespace; private String name; + private final Set permissions = new HashSet<>(); @XmlElement(name = "public") private boolean publicReadable = false; private boolean archived = false; @@ -117,14 +118,20 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per * @param contact email address of a person who is responsible for * this repository. * @param description a short description of the repository + * @param permissions permissions for specific users and groups. */ - public Repository(String id, String type, String namespace, String name, String contact, String description) { + public Repository(String id, String type, String namespace, String name, String contact, + String description, RepositoryPermission... permissions) { this.id = id; this.type = type; this.namespace = namespace; this.name = name; this.contact = contact; this.description = description; + + if (Util.isNotEmpty(permissions)) { + this.permissions.addAll(Arrays.asList(permissions)); + } } /** @@ -193,6 +200,10 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return new NamespaceAndName(getNamespace(), getName()); } + public Collection getPermissions() { + return Collections.unmodifiableCollection(permissions); + } + /** * Returns the type (hg, git, svn ...) of the {@link Repository}. * @@ -285,6 +296,19 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per this.name = name; } + public void setPermissions(Collection permissions) { + this.permissions.clear(); + this.permissions.addAll(permissions); + } + + public void addPermission(RepositoryPermission newPermission) { + this.permissions.add(newPermission); + } + + public void removePermission(RepositoryPermission permission) { + this.permissions.remove(permission); + } + public void setPublicReadable(boolean publicReadable) { this.publicReadable = publicReadable; } @@ -322,6 +346,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per repository.setCreationDate(creationDate); repository.setLastModified(lastModified); repository.setDescription(description); + repository.setPermissions(permissions); repository.setPublicReadable(publicReadable); repository.setArchived(archived); @@ -353,6 +378,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per && Objects.equal(description, other.description) && Objects.equal(publicReadable, other.publicReadable) && Objects.equal(archived, other.archived) + && Objects.equal(permissions, other.permissions) && Objects.equal(type, other.type) && Objects.equal(creationDate, other.creationDate) && Objects.equal(lastModified, other.lastModified) @@ -363,7 +389,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per @Override public int hashCode() { return Objects.hashCode(id, namespace, name, contact, description, publicReadable, - archived, type, creationDate, lastModified, properties, + archived, permissions, type, creationDate, lastModified, properties, healthCheckFailures); } @@ -377,6 +403,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per .add("description", description) .add("publicReadable", publicReadable) .add("archived", archived) + .add("permissions", permissions) .add("type", type) .add("lastModified", lastModified) .add("creationDate", creationDate) diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java index 492142674d..70d0c8491e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java @@ -41,8 +41,10 @@ import sonia.scm.security.PermissionObject; 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.Serializable; +import java.util.Collection; //~--- JDK imports ------------------------------------------------------------ @@ -60,7 +62,8 @@ public class RepositoryPermission implements PermissionObject, Serializable private boolean groupPermission = false; private String name; - private String verb; + @XmlElement(name = "verb") + private Collection verbs; /** * Constructs a new {@link RepositoryPermission}. @@ -68,10 +71,10 @@ public class RepositoryPermission implements PermissionObject, Serializable */ public RepositoryPermission() {} - public RepositoryPermission(String name, String verb, boolean groupPermission) + public RepositoryPermission(String name, Collection verbs, boolean groupPermission) { this.name = name; - this.verb = verb; + this.verbs = verbs; this.groupPermission = groupPermission; } @@ -101,7 +104,7 @@ public class RepositoryPermission implements PermissionObject, Serializable final RepositoryPermission other = (RepositoryPermission) obj; return Objects.equal(name, other.name) - && Objects.equal(verb, other.verb) + && Objects.equal(verbs, other.verbs) && Objects.equal(groupPermission, other.groupPermission); } @@ -114,7 +117,7 @@ public class RepositoryPermission implements PermissionObject, Serializable @Override public int hashCode() { - return Objects.hashCode(name, verb, groupPermission); + return Objects.hashCode(name, verbs, groupPermission); } @@ -124,7 +127,7 @@ public class RepositoryPermission implements PermissionObject, Serializable //J- return MoreObjects.toStringHelper(this) .add("name", name) - .add("verb", verb) + .add("verbs", verbs) .add("groupPermission", groupPermission) .toString(); //J+ @@ -150,9 +153,9 @@ public class RepositoryPermission implements PermissionObject, Serializable * * @return verb of the permission */ - public String getVerb() + public Collection getVerbs() { - return verb; + return verbs; } /** @@ -195,10 +198,10 @@ public class RepositoryPermission implements PermissionObject, Serializable * Sets the verb of the permission. * * - * @param verb verb of the permission + * @param verbs verbs of the permission */ - public void setVerb(String verb) + public void setVerbs(Collection verbs) { - this.verb = verb; + this.verbs = verbs; } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java index cfcd279692..a9bd5c2424 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java @@ -8,6 +8,7 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; 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; @@ -22,6 +23,9 @@ 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 { private static final int DEFAULT_PAGE_SIZE = 10; @@ -96,8 +100,7 @@ public class RepositoryCollectionResource { private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) { Repository repository = dtoToRepositoryMapper.map(repositoryDto, null); - // TODO RP -// repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), PermissionType.OWNER))); + repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), singletonList("*"), false))); return repository; } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index b66a274f0d..9fe13a71d8 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -46,6 +46,7 @@ import java.util.stream.Stream; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -78,12 +79,12 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { private static final String PERMISSION_TEST_PAYLOAD = "{ \"name\" : \"permission_name\", \"type\" : \"READ\" }"; private static final ArrayList TEST_PERMISSIONS = Lists .newArrayList( - new RepositoryPermission("user_write", "read,modify", false), - new RepositoryPermission("user_read", "read", false), - new RepositoryPermission("user_owner", "read,modify,delete", false), - new RepositoryPermission("group_read", "read", true), - new RepositoryPermission("group_write", "read,modify", true), - new RepositoryPermission("group_owner", "read,modify,delete", true) + new RepositoryPermission("user_write", asList("read","modify"), false), + new RepositoryPermission("user_read", asList("read"), false), + new RepositoryPermission("user_owner", asList("*"), false), + new RepositoryPermission("group_read", asList("read"), true), + new RepositoryPermission("group_write", asList("read","modify"), true), + new RepositoryPermission("group_owner", asList("*"), true) ); private final ExpectedRequest requestGETAllPermissions = new ExpectedRequest() .description("GET all permissions") @@ -258,7 +259,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { @Test public void shouldGetCreatedPermissions() throws URISyntaxException { createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE); - RepositoryPermission newPermission = new RepositoryPermission("new_group_perm", "read,modify", true); + RepositoryPermission newPermission = new RepositoryPermission("new_group_perm", asList("read","modify"), true); ArrayList permissions = Lists.newArrayList(TEST_PERMISSIONS); permissions.add(newPermission); ImmutableList expectedPermissions = ImmutableList.copyOf(permissions); @@ -287,7 +288,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE); RepositoryPermission modifiedPermission = TEST_PERMISSIONS.get(0); // modify the type to owner - modifiedPermission.setVerb("read,modify,delete"); + modifiedPermission.setVerbs(asList("read", "modify", "delete")); ImmutableList expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS); assertExpectedRequest(requestPUTPermission .content("{\"name\" : \"" + modifiedPermission.getName() + "\" , \"type\" : \"OWNER\" , \"groupPermission\" : false}") @@ -381,7 +382,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { RepositoryPermissionDto result = new RepositoryPermissionDto(); result.setName(permission.getName()); result.setGroupPermission(permission.isGroupPermission()); - result.setType(permission.getVerb()); +// result.setType(permission.getVerbs()); TODO RP String permissionName = Optional.of(permission.getName()) .filter(p -> !permission.isGroupPermission()) .orElse(GROUP_PREFIX + permission.getName()); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionToRepositoryPermissionDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionToRepositoryPermissionDtoMapperTest.java index 09a3ab4855..97146e67ff 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionToRepositoryPermissionDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionToRepositoryPermissionDtoMapperTest.java @@ -12,6 +12,7 @@ import sonia.scm.repository.Repository; import java.net.URI; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; @RunWith(MockitoJUnitRunner.Silent.class) @@ -35,7 +36,7 @@ public class RepositoryPermissionToRepositoryPermissionDtoMapperTest { @SubjectAware(username = "trillian", password = "secret") public void shouldMapGroupPermissionCorrectly() { Repository repository = getDummyRepository(); - RepositoryPermission permission = new RepositoryPermission("42", "read,modify,delete", true); + RepositoryPermission permission = new RepositoryPermission("42", asList("read","modify","delete"), true); RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository); @@ -47,7 +48,7 @@ public class RepositoryPermissionToRepositoryPermissionDtoMapperTest { @SubjectAware(username = "trillian", password = "secret") public void shouldMapNonGroupPermissionCorrectly() { Repository repository = getDummyRepository(); - RepositoryPermission permission = new RepositoryPermission("42", "read,modify,delete", false); + RepositoryPermission permission = new RepositoryPermission("42", asList("read","modify","delete"), false); RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository); diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java index bfb97d0b40..d04c35686f 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java @@ -58,6 +58,7 @@ import sonia.scm.repository.RepositoryTestData; import sonia.scm.user.User; import sonia.scm.user.UserTestData; +import static java.util.Arrays.asList; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.nullValue; @@ -228,7 +229,7 @@ public class DefaultAuthorizationCollectorTest { // heartOfGold.setPermissions(Lists.newArrayList(new RepositoryPermission("trillian"))); Repository puzzle42 = RepositoryTestData.create42Puzzle(); puzzle42.setId("two"); - RepositoryPermission permission = new RepositoryPermission(group, "read,modify", true); + RepositoryPermission permission = new RepositoryPermission(group, asList("read","modify"), true); // puzzle42.setPermissions(Lists.newArrayList(permission)); when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42)); From 02b19e51ef71ac180a431c356d832e21e6fdaf11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 09:47:38 +0100 Subject: [PATCH 06/48] Fix repository permission rest interface --- .../java/sonia/scm/repository/Repository.java | 1 + .../repository/xml/XmlRepositoryDAOTest.java | 17 +++++ .../java/sonia/scm/it/PermissionsITCase.java | 16 +++-- .../java/sonia/scm/it/utils/TestData.java | 40 ++++++----- ...sitoryPermissionCollectionToDtoMapper.java | 11 ++- .../v2/resources/RepositoryPermissionDto.java | 14 +--- .../RepositoryPermissionRootResource.java | 70 ++++++++----------- .../DefaultAuthorizationCollector.java | 10 +-- .../META-INF/scm/repository-permissions.xml | 2 +- .../RepositoryPermissionRootResourceTest.java | 30 ++++---- .../resources/RepositoryRootResourceTest.java | 16 ++--- .../test/java/sonia/scm/it/GitLfsITCase.java | 11 --- .../DefaultAuthorizationCollectorTest.java | 7 +- 13 files changed, 119 insertions(+), 126 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 3adb215d27..568b75e525 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -80,6 +80,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per private Long lastModified; private String namespace; private String name; + @XmlElement(name = "permission") private final Set permissions = new HashSet<>(); @XmlElement(name = "public") private boolean publicReadable = false; diff --git a/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java b/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java index 6330db56a0..68d8803c89 100644 --- a/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java +++ b/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java @@ -16,6 +16,7 @@ import sonia.scm.io.FileSystem; import sonia.scm.repository.InitialRepositoryLocationResolver; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.RepositoryTestData; import java.io.IOException; @@ -23,9 +24,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Clock; +import java.util.Arrays; import java.util.Collection; import java.util.concurrent.atomic.AtomicLong; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -329,6 +332,20 @@ class XmlRepositoryDAOTest { assertThat(content).contains("Awesome Spaceship"); } + @Test + void x() throws IOException { + Repository heartOfGold = createHeartOfGold(); + heartOfGold.setPermissions(asList(new RepositoryPermission("trillian", asList("read", "write"), false), new RepositoryPermission("vorgons", asList("delete"), true))); + dao.add(heartOfGold); + + Path repositoryDirectory = getAbsolutePathFromDao(heartOfGold.getId()); + Path metadataPath = dao.resolveMetadataPath(repositoryDirectory); + + String content = content(metadataPath); + System.out.println(content); + assertThat(content).contains("Awesome Spaceship"); + } + @Test void shouldReadPathDatabaseAndMetadataOfRepositories() { Repository heartOfGold = createHeartOfGold(); diff --git a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java index 15f5e30abc..1f61cdb93a 100644 --- a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java @@ -58,7 +58,10 @@ import static org.junit.Assert.assertNull; import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile; import static sonia.scm.it.utils.RestUtil.given; import static sonia.scm.it.utils.ScmTypes.availableScmTypes; +import static sonia.scm.it.utils.TestData.OWNER; +import static sonia.scm.it.utils.TestData.READ; import static sonia.scm.it.utils.TestData.USER_SCM_ADMIN; +import static sonia.scm.it.utils.TestData.WRITE; import static sonia.scm.it.utils.TestData.callRepository; @RunWith(Parameterized.class) @@ -90,13 +93,12 @@ public class PermissionsITCase { public void prepareEnvironment() { TestData.createDefault(); TestData.createNotAdminUser(USER_READ, USER_PASS); - // TODO RP -// TestData.createUserPermission(USER_READ, PermissionType.READ, repositoryType); -// TestData.createNotAdminUser(USER_WRITE, USER_PASS); -// TestData.createUserPermission(USER_WRITE, PermissionType.WRITE, repositoryType); -// TestData.createNotAdminUser(USER_OWNER, USER_PASS); -// TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType); -// TestData.createNotAdminUser(USER_OTHER, USER_PASS); + TestData.createUserPermission(USER_READ, READ, repositoryType); + TestData.createNotAdminUser(USER_WRITE, USER_PASS); + TestData.createUserPermission(USER_WRITE, WRITE, repositoryType); + TestData.createNotAdminUser(USER_OWNER, USER_PASS); + TestData.createUserPermission(USER_OWNER, OWNER, repositoryType); + TestData.createNotAdminUser(USER_OTHER, USER_PASS); createdPermissions = asList(USER_READ, USER_WRITE, USER_OWNER); } diff --git a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java index fb92628287..1a2394edda 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java @@ -9,9 +9,11 @@ import sonia.scm.web.VndMediaType; import javax.json.Json; import javax.json.JsonObjectBuilder; import java.net.URI; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static java.util.Arrays.asList; import static sonia.scm.it.utils.RestUtil.createResourceUrl; @@ -24,6 +26,11 @@ public class TestData { public static final String USER_SCM_ADMIN = "scmadmin"; public static final String USER_ANONYMOUS = "anonymous"; + + public static final Collection READ = asList("read", "pull"); + public static final Collection WRITE = asList("read", "write", "pull", "push"); + public static final Collection OWNER = asList("*"); + private static final List PROTECTED_USERS = asList(USER_SCM_ADMIN, USER_ANONYMOUS); private static Map DEFAULT_REPOSITORIES = new HashMap<>(); @@ -81,23 +88,22 @@ public class TestData { ; } - // TODO RP -// public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) { -// String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType); -// LOG.info("create permission with name {} and type: {} using the endpoint: {}", name, permissionType, defaultPermissionUrl); -// given(VndMediaType.PERMISSION) -// .when() -// .content("{\n" + -// "\t\"type\": \"" + permissionType.name() + "\",\n" + -// "\t\"name\": \"" + name + "\",\n" + -// "\t\"groupPermission\": false\n" + -// "\t\n" + -// "}") -// .post(defaultPermissionUrl) -// .then() -// .statusCode(HttpStatus.SC_CREATED) -// ; -// } + public static void createUserPermission(String name, Collection permissionType, String repositoryType) { + String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType); + LOG.info("create permission with name {} and type: {} using the endpoint: {}", name, permissionType, defaultPermissionUrl); + given(VndMediaType.PERMISSION) + .when() + .content("{\n" + + "\t\"verbs\": " + permissionType.stream().collect(Collectors.joining("\",\"", "[\"", "\"]")) + ",\n" + + "\t\"name\": \"" + name + "\",\n" + + "\t\"groupPermission\": false\n" + + "\t\n" + + "}") + .post(defaultPermissionUrl) + .then() + .statusCode(HttpStatus.SC_CREATED) + ; + } public static List getUserPermissions(String username, String password, String repositoryType) { return callUserPermissions(username, password, repositoryType, HttpStatus.SC_OK) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java index 9c05f80de4..5e678212e8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionCollectionToDtoMapper.java @@ -26,12 +26,11 @@ public class RepositoryPermissionCollectionToDtoMapper { } public HalRepresentation map(Repository repository) { -// List repositoryPermissionDtoList = repository.getPermissions() -// .stream() -// .map(permission -> repositoryPermissionToRepositoryPermissionDtoMapper.map(permission, repository)) -// .collect(toList()); -// return new HalRepresentation(createLinks(repository), embedDtos(repositoryPermissionDtoList)); - return new HalRepresentation(createLinks(repository)); + List repositoryPermissionDtoList = repository.getPermissions() + .stream() + .map(permission -> repositoryPermissionToRepositoryPermissionDtoMapper.map(permission, repository)) + .collect(toList()); + return new HalRepresentation(createLinks(repository), embedDtos(repositoryPermissionDtoList)); } private Links createLinks(Repository repository) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java index 6e6b9fd7fc..0699b78e91 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java @@ -10,6 +10,8 @@ import lombok.ToString; 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 +22,7 @@ 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; - + private Collection verbs; private boolean groupPermission = false; @@ -38,7 +31,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) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java index 9c22a5e1b0..97ba519df8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java @@ -37,16 +37,19 @@ import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX; @Slf4j public class RepositoryPermissionRootResource { - private RepositoryPermissionDtoToRepositoryPermissionMapper dtoToModelMapper; private RepositoryPermissionToRepositoryPermissionDtoMapper modelToDtoMapper; private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper; private ResourceLinks resourceLinks; private final RepositoryManager manager; - @Inject - public RepositoryPermissionRootResource(RepositoryPermissionDtoToRepositoryPermissionMapper 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 RepositoryPermissionRootResource { this.manager = manager; } - /** * Adds a new permission to the user or group managed by the repository * @@ -73,19 +75,17 @@ public class RepositoryPermissionRootResource { @TypeHint(TypeHint.NO_CONTENT.class) @Consumes(VndMediaType.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(); checkPermissionAlreadyExists(permission, repository); - // TODO RP -// repository.addPermission(dtoToModelMapper.map(permission)); + repository.addPermission(dtoToModelMapper.map(permission)); manager.modify(repository); String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission); return Response.created(URI.create(resourceLinks.repositoryPermission().self(namespace, name, urlPermissionName))).build(); } - /** * Get the searched permission with permission name related to a repository * @@ -107,17 +107,15 @@ public class RepositoryPermissionRootResource { Repository repository = load(namespace, name); RepositoryPermissions.permissionRead(repository).check(); return Response.ok( - // TODO RP -// repository.getPermissions() -// .stream() -// .filter(filterPermission(permissionName)) -// .map(permission -> modelToDtoMapper.map(permission, repository)) -// .findFirst() -// .orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name))) + repository.getPermissions() + .stream() + .filter(filterPermission(permissionName)) + .map(permission -> modelToDtoMapper.map(permission, repository)) + .findFirst() + .orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name))) ).build(); } - /** * Get all permissions related to a repository * @@ -141,7 +139,6 @@ public class RepositoryPermissionRootResource { 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) @@ -175,13 +172,12 @@ public class RepositoryPermissionRootResource { checkPermissionAlreadyExists(permission, repository); } - // TODO RP -// RepositoryPermission existingPermission = repository.getPermissions() -// .stream() -// .filter(filterPermission(permissionName)) -// .findFirst() -// .orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name))); -// dtoToModelMapper.modify(existingPermission, permission); + RepositoryPermission existingPermission = repository.getPermissions() + .stream() + .filter(filterPermission(permissionName)) + .findFirst() + .orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name))); + dtoToModelMapper.modify(existingPermission, permission); manager.modify(repository); log.info("the permission with name: {} is updated.", permissionName); return Response.noContent().build(); @@ -208,22 +204,20 @@ public class RepositoryPermissionRootResource { log.info("try to delete the permission with name: {}.", permissionName); Repository repository = load(namespace, name); RepositoryPermissions.modify(repository).check(); - // TODO RP -// repository.getPermissions() -// .stream() -// .filter(filterPermission(permissionName)) -// .findFirst() -// .ifPresent(repository::removePermission) -// ; + repository.getPermissions() + .stream() + .filter(filterPermission(permissionName)) + .findFirst() + .ifPresent(repository::removePermission); manager.modify(repository); log.info("the permission with name: {} is updated.", permissionName); return Response.noContent().build(); } - Predicate filterPermission(String permissionName) { - return permission -> getPermissionName(permissionName).equals(permission.getName()) + private Predicate filterPermission(String name) { + return permission -> getPermissionName(name).equals(permission.getName()) && - permission.isGroupPermission() == isGroupPermission(permissionName); + permission.isGroupPermission() == isGroupPermission(name); } private String getPermissionName(String permissionName) { @@ -236,7 +230,6 @@ public class RepositoryPermissionRootResource { 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 @@ -266,10 +259,9 @@ public class RepositoryPermissionRootResource { } private boolean isPermissionExist(RepositoryPermissionDto permission, Repository repository) { - return true; -// return repository.getPermissions() -// .stream() -// .anyMatch(p -> p.getName().equals(permission.getName()) && p.isGroupPermission() == permission.isGroupPermission()); + return repository.getPermissions() + .stream() + .anyMatch(p -> p.getName().equals(permission.getName()) && p.isGroupPermission() == permission.isGroupPermission()); } } diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java index e015ed90f6..1e0bffa568 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java @@ -63,7 +63,6 @@ import sonia.scm.user.UserPermissions; import sonia.scm.util.Util; import java.util.Collection; -import java.util.Collections; import java.util.Set; //~--- JDK imports ------------------------------------------------------------ @@ -199,12 +198,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector private void collectRepositoryPermissions(Builder builder, Repository repository, User user, GroupNames groups) { - - // TODO RP - - Collection repositoryPermissions - = Collections.emptyList(); -// = repository.getPermissions(); + Collection repositoryPermissions = repository.getPermissions(); if (Util.isNotEmpty(repositoryPermissions)) { @@ -214,7 +208,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector hasPermission = isUserPermitted(user, groups, permission); if (hasPermission) { - String perm = null; // TODO RP 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 {}", diff --git a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml index 9df450efb0..89d47e156f 100644 --- a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml +++ b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml @@ -14,8 +14,8 @@ WRITER read - push pull + push diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index 9fe13a71d8..6f44b8d522 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -1,5 +1,6 @@ package sonia.scm.api.v2.resources; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; @@ -29,9 +30,9 @@ import org.junit.jupiter.api.TestFactory; import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.repository.NamespaceAndName; -import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; +import sonia.scm.repository.RepositoryPermission; import sonia.scm.web.VndMediaType; import java.io.IOException; @@ -47,6 +48,7 @@ import java.util.stream.Stream; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -76,7 +78,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { private static final String PERMISSION_NAME = "perm"; private static final String PATH_OF_ALL_PERMISSIONS = REPOSITORY_NAMESPACE + "/" + REPOSITORY_NAME + "/permissions/"; private static final String PATH_OF_ONE_PERMISSION = PATH_OF_ALL_PERMISSIONS + PERMISSION_NAME; - private static final String PERMISSION_TEST_PAYLOAD = "{ \"name\" : \"permission_name\", \"type\" : \"READ\" }"; + private static final String PERMISSION_TEST_PAYLOAD = "{ \"name\" : \"permission_name\", \"verbs\" : [\"read\",\"pull\"] }"; private static final ArrayList TEST_PERMISSIONS = Lists .newArrayList( new RepositoryPermission("user_write", asList("read","modify"), false), @@ -232,7 +234,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { public void shouldGet400OnCreatingNewPermissionWithNotAllowedCharacters() throws URISyntaxException { // the @ character at the begin of the name is not allowed createUserWithRepository("user"); - String permissionJson = "{ \"name\": \"@permission\", \"type\": \"OWNER\" }"; + String permissionJson = "{ \"name\": \"@permission\", \"verbs\": [\"*\"] }"; MockHttpRequest request = MockHttpRequest .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS) .content(permissionJson.getBytes()) @@ -244,7 +246,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { assertEquals(400, response.getStatus()); // the whitespace at the begin opf the name is not allowed - permissionJson = "{ \"name\": \" permission\", \"type\": \"OWNER\" }"; + permissionJson = "{ \"name\": \" permission\", \"verbs\": [\"*\"] }"; request = MockHttpRequest .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS) .content(permissionJson.getBytes()) @@ -259,12 +261,12 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { @Test public void shouldGetCreatedPermissions() throws URISyntaxException { createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE); - RepositoryPermission newPermission = new RepositoryPermission("new_group_perm", asList("read","modify"), true); + RepositoryPermission newPermission = new RepositoryPermission("new_group_perm", asList("read", "pull", "push"), true); ArrayList permissions = Lists.newArrayList(TEST_PERMISSIONS); permissions.add(newPermission); ImmutableList expectedPermissions = ImmutableList.copyOf(permissions); assertExpectedRequest(requestPOSTPermission - .content("{\"name\" : \"" + newPermission.getName() + "\" , \"type\" : \"WRITE\" , \"groupPermission\" : true}") + .content("{\"name\" : \"" + newPermission.getName() + "\" , \"verbs\" : [\"read\",\"pull\",\"push\"], \"groupPermission\" : true}") .expectedResponseStatus(201) .responseValidator(response -> assertThat(response.getContentAsString()) .as("POST response has no body") @@ -278,7 +280,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE); RepositoryPermission newPermission = TEST_PERMISSIONS.get(0); assertExpectedRequest(requestPOSTPermission - .content("{\"name\" : \"" + newPermission.getName() + "\" , \"type\" : \"WRITE\" , \"groupPermission\" : false}") + .content("{\"name\" : \"" + newPermission.getName() + "\" , \"verbs\" : [\"read\",\"pull\",\"push\"], \"groupPermission\" : false}") .expectedResponseStatus(409) ); } @@ -288,10 +290,10 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE); RepositoryPermission modifiedPermission = TEST_PERMISSIONS.get(0); // modify the type to owner - modifiedPermission.setVerbs(asList("read", "modify", "delete")); + modifiedPermission.setVerbs(new ArrayList<>(singletonList("*"))); ImmutableList expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS); assertExpectedRequest(requestPUTPermission - .content("{\"name\" : \"" + modifiedPermission.getName() + "\" , \"type\" : \"OWNER\" , \"groupPermission\" : false}") + .content("{\"name\" : \"" + modifiedPermission.getName() + "\" , \"verbs\" : [\"*\"], \"groupPermission\" : false}") .path(PATH_OF_ALL_PERMISSIONS + modifiedPermission.getName()) .expectedResponseStatus(204) .responseValidator(response -> assertThat(response.getContentAsString()) @@ -353,7 +355,10 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { .map(hal -> { RepositoryPermissionDto result = new RepositoryPermissionDto(); result.setName(hal.getAttribute("name").asText()); - result.setType(hal.getAttribute("type").asText()); + JsonNode attribute = hal.getAttribute("verbs"); + List verbs = new ArrayList<>(); + attribute.iterator().forEachRemaining(v -> verbs.add(v.asText())); + result.setVerbs(verbs); result.setGroupPermission(hal.getAttribute("groupPermission").asBoolean()); result.add(hal.getLinks()); return result; @@ -382,7 +387,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { RepositoryPermissionDto result = new RepositoryPermissionDto(); result.setName(permission.getName()); result.setGroupPermission(permission.isGroupPermission()); -// result.setType(permission.getVerbs()); TODO RP + result.setVerbs(permission.getVerbs()); String permissionName = Optional.of(permission.getName()) .filter(p -> !permission.isGroupPermission()) .orElse(GROUP_PREFIX + permission.getName()); @@ -412,8 +417,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { } private void createUserWithRepositoryAndPermissions(ArrayList permissions, String userPermission) { - // TODO RP -// createUserWithRepository(userPermission).setPermissions(permissions); + createUserWithRepository(userPermission).setPermissions(permissions); } private Stream createDynamicTestsToAssertResponses(ExpectedRequest... expectedRequests) { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index a18b63c53f..bf4366f0b2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -38,9 +38,8 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import static javax.servlet.http.HttpServletResponse.SC_OK; import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; @@ -286,13 +285,12 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { dispatcher.invoke(request, response); - // TODO RP -// Assertions.assertThat(createCaptor.getValue().getPermissions()) -// .hasSize(1) -// .allSatisfy(p -> { -// assertThat(p.getName()).isEqualTo("trillian"); -// assertThat(p.getType()).isEqualTo(PermissionType.OWNER); -// }); + assertThat(createCaptor.getValue().getPermissions()) + .hasSize(1) + .allSatisfy(p -> { + assertThat(p.getName()).isEqualTo("trillian"); + assertThat(p.getVerbs()).containsExactly("*"); + }); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java index 242c9b3047..a377337eea 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java @@ -116,17 +116,6 @@ public class GitLfsITCase { @Test public void testLfsAPIWithOwnerPermissions() throws IOException { - // TODO RP - uploadAndDownloadAsUser(); - } - - @Test - public void testLfsAPIWithWritePermissions() throws IOException { - // TODO RP - uploadAndDownloadAsUser(); - } - - private void uploadAndDownloadAsUser() throws IOException { User trillian = UserTestData.createTrillian(); trillian.setPassword("secret123"); createUser(trillian); diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java index d04c35686f..e9345c9599 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java @@ -225,12 +225,11 @@ public class DefaultAuthorizationCollectorTest { authenticate(UserTestData.createTrillian(), group); Repository heartOfGold = RepositoryTestData.createHeartOfGold(); heartOfGold.setId("one"); - // TODO RP -// heartOfGold.setPermissions(Lists.newArrayList(new RepositoryPermission("trillian"))); + heartOfGold.setPermissions(Lists.newArrayList(new RepositoryPermission("trillian", asList("read", "pull"), false))); Repository puzzle42 = RepositoryTestData.create42Puzzle(); puzzle42.setId("two"); - RepositoryPermission permission = new RepositoryPermission(group, asList("read","modify"), true); -// puzzle42.setPermissions(Lists.newArrayList(permission)); + RepositoryPermission permission = new RepositoryPermission(group, asList("read", "pull", "push"), true); + puzzle42.setPermissions(Lists.newArrayList(permission)); when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42)); // execute and assert From 8a0d2ba81903dbdb54863094deeb3b044ee9b02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 10:06:48 +0100 Subject: [PATCH 07/48] Rename --- ...signer.java => RepositoryPermissions.java} | 7 ++-- .../RepositoryPermissionAssignerTest.java | 25 ------------- .../security/RepositoryPermissionsTest.java | 36 +++++++++++++++++++ 3 files changed, 39 insertions(+), 29 deletions(-) rename scm-webapp/src/main/java/sonia/scm/security/{RepositoryPermissionAssigner.java => RepositoryPermissions.java} (95%) delete mode 100644 scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionAssignerTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionsTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissions.java similarity index 95% rename from scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java rename to scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissions.java index 59bc489144..463c2def6e 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionAssigner.java +++ b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissions.java @@ -21,16 +21,15 @@ import java.util.Enumeration; import java.util.List; import java.util.stream.Collectors; -public class RepositoryPermissionAssigner { +public class RepositoryPermissions { - private static final Logger logger = LoggerFactory.getLogger(RepositoryPermissionAssigner.class); - private static final String NAME = "permissions"; + private static final Logger logger = LoggerFactory.getLogger(RepositoryPermissions.class); private static final String REPOSITORY_PERMISSION_DESCRIPTOR = "META-INF/scm/repository-permissions.xml"; private final ConfigurationEntryStoreFactory storeFactory; private final AvailableRepositoryPermissions availablePermissions; @Inject - public RepositoryPermissionAssigner(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader) { + public RepositoryPermissions(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader) { this.storeFactory = storeFactory; this.availablePermissions = readAvailablePermissions(pluginLoader); } diff --git a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionAssignerTest.java b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionAssignerTest.java deleted file mode 100644 index d037c2cbfe..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionAssignerTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package sonia.scm.security; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import sonia.scm.plugin.PluginLoader; -import sonia.scm.store.ConfigurationEntryStoreFactory; -import sonia.scm.util.ClassLoaders; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class RepositoryPermissionAssignerTest { - - @Test - void x() { - PluginLoader pluginLoader = mock(PluginLoader.class); - when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class)); - ConfigurationEntryStoreFactory configurationEntryStoreFactory = mock(ConfigurationEntryStoreFactory.class); - RepositoryPermissionAssigner repositoryPermissionAssigner = new RepositoryPermissionAssigner(configurationEntryStoreFactory, pluginLoader); - Assertions.assertThat(repositoryPermissionAssigner.availableVerbs()).isNotEmpty(); - Assertions.assertThat(repositoryPermissionAssigner.availableRoles()).isNotEmpty().noneMatch(r -> r.getVerbs().isEmpty()); - System.out.println(repositoryPermissionAssigner.availableVerbs()); - System.out.println(repositoryPermissionAssigner.availableRoles()); - } -} diff --git a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionsTest.java b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionsTest.java new file mode 100644 index 0000000000..433e91e64c --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionsTest.java @@ -0,0 +1,36 @@ +package sonia.scm.security; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.store.ConfigurationEntryStoreFactory; +import sonia.scm.util.ClassLoaders; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class RepositoryPermissionsTest { + + private RepositoryPermissions repositoryPermissions; + + @BeforeEach + void init() { + PluginLoader pluginLoader = mock(PluginLoader.class); + when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class)); + ConfigurationEntryStoreFactory configurationEntryStoreFactory = mock(ConfigurationEntryStoreFactory.class); + repositoryPermissions = new RepositoryPermissions(configurationEntryStoreFactory, pluginLoader); + } + + @Test + void shouldReadAvailableRoles() { + Assertions.assertThat(repositoryPermissions.availableRoles()).isNotEmpty().noneMatch(r -> r.getVerbs().isEmpty()); + System.out.println(repositoryPermissions.availableRoles()); + } + + @Test + void shouldReadAvailableVerbs() { + Assertions.assertThat(repositoryPermissions.availableVerbs()).isNotEmpty(); + System.out.println(repositoryPermissions.availableVerbs()); + } +} From d011c82def6c931253da543880a6a58d62a0d91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 10:13:21 +0100 Subject: [PATCH 08/48] Fix unit test --- .../java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java b/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java index 68d8803c89..9c289b061c 100644 --- a/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java +++ b/scm-dao-xml/src/test/java/sonia/scm/repository/xml/XmlRepositoryDAOTest.java @@ -333,7 +333,7 @@ class XmlRepositoryDAOTest { } @Test - void x() throws IOException { + void shouldPersistPermissions() throws IOException { Repository heartOfGold = createHeartOfGold(); heartOfGold.setPermissions(asList(new RepositoryPermission("trillian", asList("read", "write"), false), new RepositoryPermission("vorgons", asList("delete"), true))); dao.add(heartOfGold); @@ -343,7 +343,7 @@ class XmlRepositoryDAOTest { String content = content(metadataPath); System.out.println(content); - assertThat(content).contains("Awesome Spaceship"); + assertThat(content).containsSubsequence("trillian", "read", "write", "vorgons", "delete"); } @Test From 8c049446235e31d7474c7892321a3cdf519d3d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 11:18:02 +0100 Subject: [PATCH 09/48] Do not return null --- .../main/java/sonia/scm/repository/RepositoryPermission.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java index 70d0c8491e..97872f29eb 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java @@ -46,6 +46,8 @@ import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.Collection; +import static java.util.Collections.emptyList; + //~--- JDK imports ------------------------------------------------------------ /** @@ -155,7 +157,7 @@ public class RepositoryPermission implements PermissionObject, Serializable */ public Collection getVerbs() { - return verbs; + return verbs == null? emptyList(): verbs; } /** From b8679d1f8302f48ff2350747b4d3deaf053b21b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 11:18:47 +0100 Subject: [PATCH 10/48] Fix permission check --- .../src/main/java/sonia/scm/web/filter/PermissionFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java index 328494a626..a062fdb360 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java @@ -252,7 +252,7 @@ public abstract class PermissionFilter extends ScmProviderHttpServletDecorator } else { - permitted = RepositoryPermissions.read(repository).isPermitted(); + permitted = RepositoryPermissions.pull(repository).isPermitted(); } return permitted; From 6bf216d3798ab4875805e216a2dfe515ec3422a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 11:19:55 +0100 Subject: [PATCH 11/48] Rename media type --- .../src/main/java/sonia/scm/web/VndMediaType.java | 2 +- .../test/java/sonia/scm/it/PermissionsITCase.java | 6 +++--- .../src/test/java/sonia/scm/it/utils/TestData.java | 4 ++-- .../resources/RepositoryPermissionRootResource.java | 8 ++++---- .../RepositoryPermissionRootResourceTest.java | 6 +++--- .../src/test/java/sonia/scm/it/GitLfsITCase.java | 12 ++++++------ 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 8596bab754..0836f177ee 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -20,7 +20,7 @@ public class VndMediaType { public static final String GROUP = PREFIX + "group" + SUFFIX; public static final String AUTOCOMPLETE = PREFIX + "autocomplete" + SUFFIX; public static final String REPOSITORY = PREFIX + "repository" + SUFFIX; - public static final String PERMISSION = PREFIX + "permission" + SUFFIX; + public static final String REPOSITORY_PERMISSION = PREFIX + "repositoryPermission" + SUFFIX; public static final String CHANGESET = PREFIX + "changeset" + SUFFIX; public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX; public static final String MODIFICATIONS = PREFIX + "modifications" + SUFFIX; diff --git a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java index 1f61cdb93a..926be5459f 100644 --- a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java @@ -111,7 +111,7 @@ public class PermissionsITCase { @Test public void readUserShouldNotSeeBruteForcePermissions() { - given(VndMediaType.PERMISSION, USER_READ, USER_PASS) + given(VndMediaType.REPOSITORY_PERMISSION, USER_READ, USER_PASS) .when() .get(TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType)) .then() @@ -127,7 +127,7 @@ public class PermissionsITCase { @Test public void writeUserShouldNotSeeBruteForcePermissions() { - given(VndMediaType.PERMISSION, USER_WRITE, USER_PASS) + given(VndMediaType.REPOSITORY_PERMISSION, USER_WRITE, USER_PASS) .when() .get(TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType)) .then() @@ -147,7 +147,7 @@ public class PermissionsITCase { @Test public void otherUserShouldNotSeeBruteForcePermissions() { - given(VndMediaType.PERMISSION, USER_OTHER, USER_PASS) + given(VndMediaType.REPOSITORY_PERMISSION, USER_OTHER, USER_PASS) .when() .get(TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType)) .then() diff --git a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java index 1a2394edda..584737221f 100644 --- a/scm-it/src/test/java/sonia/scm/it/utils/TestData.java +++ b/scm-it/src/test/java/sonia/scm/it/utils/TestData.java @@ -91,7 +91,7 @@ public class TestData { public static void createUserPermission(String name, Collection permissionType, String repositoryType) { String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType); LOG.info("create permission with name {} and type: {} using the endpoint: {}", name, permissionType, defaultPermissionUrl); - given(VndMediaType.PERMISSION) + given(VndMediaType.REPOSITORY_PERMISSION) .when() .content("{\n" + "\t\"verbs\": " + permissionType.stream().collect(Collectors.joining("\",\"", "[\"", "\"]")) + ",\n" + @@ -112,7 +112,7 @@ public class TestData { } public static ValidatableResponse callUserPermissions(String username, String password, String repositoryType, int expectedStatusCode) { - return given(VndMediaType.PERMISSION, username, password) + return given(VndMediaType.REPOSITORY_PERMISSION, username, password) .when() .get(TestData.getDefaultPermissionUrl(username, password, repositoryType)) .then() diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java index 97ba519df8..dd66e6e5f2 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResource.java @@ -73,7 +73,7 @@ public class RepositoryPermissionRootResource { @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) { log.info("try to add new permission: {}", permission); @@ -100,7 +100,7 @@ public class RepositoryPermissionRootResource { @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) { @@ -130,7 +130,7 @@ public class RepositoryPermissionRootResource { @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) { @@ -154,7 +154,7 @@ public class RepositoryPermissionRootResource { @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, diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java index 6f44b8d522..5d9af10d09 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryPermissionRootResourceTest.java @@ -238,7 +238,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { MockHttpRequest request = MockHttpRequest .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS) .content(permissionJson.getBytes()) - .contentType(VndMediaType.PERMISSION); + .contentType(VndMediaType.REPOSITORY_PERMISSION); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -250,7 +250,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { request = MockHttpRequest .post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS) .content(permissionJson.getBytes()) - .contentType(VndMediaType.PERMISSION); + .contentType(VndMediaType.REPOSITORY_PERMISSION); response = new MockHttpResponse(); dispatcher.invoke(request, response); @@ -430,7 +430,7 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase { HttpRequest request = MockHttpRequest .create(entry.method, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + entry.path) .content(entry.content) - .contentType(VndMediaType.PERMISSION); + .contentType(VndMediaType.REPOSITORY_PERMISSION); dispatcher.invoke(request, response); log.info("Test the Request :{}", entry); assertThat(response.getStatus()) diff --git a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java index a377337eea..c431e9c09a 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java @@ -124,8 +124,8 @@ public class GitLfsITCase { String permissionsUrl = repository.getLinks().getLinkBy("permissions").get().getHref(); IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl)) .accept("*/*") - .type(VndMediaType.PERMISSION) - .post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"type\":\"WRITE\"}"); + .type(VndMediaType.REPOSITORY_PERMISSION) + .post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"verbs\":[\"*\"]}"); ScmClient client = new ScmClient(trillian.getId(), "secret123"); @@ -165,8 +165,8 @@ public class GitLfsITCase { String permissionsUrl = repository.getLinks().getLinkBy("permissions").get().getHref(); IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl)) .accept("*/*") - .type(VndMediaType.PERMISSION) - .post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"type\":\"READ\"}"); + .type(VndMediaType.REPOSITORY_PERMISSION) + .post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"verbs\":[\"read\"]}"); ScmClient client = new ScmClient(trillian.getId(), "secret123"); uploadAndDownload(client); @@ -186,8 +186,8 @@ public class GitLfsITCase { String permissionsUrl = repository.getLinks().getLinkBy("permissions").get().getHref(); IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl)) .accept("*/*") - .type(VndMediaType.PERMISSION) - .post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"type\":\"READ\"}"); + .type(VndMediaType.REPOSITORY_PERMISSION) + .post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"verbs\":[\"read\"]}"); // upload data as admin String data = UUID.randomUUID().toString(); From 48e1e16fe6f38512168aa2ee19417cf29d69eb41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 11:20:27 +0100 Subject: [PATCH 12/48] Do not create empty permissions --- .../java/sonia/scm/security/DefaultAuthorizationCollector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java index 1e0bffa568..3fdbcdf351 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java @@ -206,7 +206,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector for (RepositoryPermission permission : repositoryPermissions) { hasPermission = isUserPermitted(user, groups, permission); - if (hasPermission) + if (hasPermission && !permission.getVerbs().isEmpty()) { String perm = "repository:" + String.join(",", permission.getVerbs()) + ":" + repository.getId(); if (logger.isTraceEnabled()) From 7e9d60fa8ddd1d74a90d22f6fd7c4b4e436c713c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 11:20:43 +0100 Subject: [PATCH 13/48] Fetch authorization exceptions --- .../main/java/sonia/scm/web/protocol/HttpProtocolServlet.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java b/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java index c6a96e4b9e..250846a8cc 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java @@ -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); } } } From fdf4421a551250586a7e35ce33f7fcfd5675bef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 12:22:06 +0100 Subject: [PATCH 14/48] Fix verbs for repository and rename class --- .../RepositoryPermissionResource.java | 50 +++++++++++++++ ...java => RepositoryPermissionProvider.java} | 11 ++-- .../META-INF/scm/repository-permissions.xml | 11 +++- .../RepositoryPermissionProviderTest.java | 62 +++++++++++++++++++ .../security/RepositoryPermissionsTest.java | 36 ----------- 5 files changed, 127 insertions(+), 43 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionResource.java rename scm-webapp/src/main/java/sonia/scm/security/{RepositoryPermissions.java => RepositoryPermissionProvider.java} (90%) create mode 100644 scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java delete mode 100644 scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionsTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionResource.java new file mode 100644 index 0000000000..2cfbf30de6 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionResource.java @@ -0,0 +1,50 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +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; +import java.util.Collection; + +/** + * 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; + + @Inject + public RepositoryPermissionResource(RepositoryPermissionProvider repositoryPermissionProvider) { + this.repositoryPermissionProvider = repositoryPermissionProvider; + } + + @GET + @Path("verbs") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @Produces(VndMediaType.REPOSITORY_TYPE_COLLECTION) + public Collection getRepositoryPermissionVerbs() { + return repositoryPermissionProvider.availableVerbs(); + } + + @GET + @Path("roles") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @Produces(VndMediaType.REPOSITORY_TYPE_COLLECTION) + public Collection getRepositoryRoles() { + return repositoryPermissionProvider.availableRoles(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissions.java b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java similarity index 90% rename from scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissions.java rename to scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java index 463c2def6e..f02c31b0ed 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissions.java +++ b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java @@ -21,15 +21,15 @@ import java.util.Enumeration; import java.util.List; import java.util.stream.Collectors; -public class RepositoryPermissions { +public class RepositoryPermissionProvider { - private static final Logger logger = LoggerFactory.getLogger(RepositoryPermissions.class); + private static final Logger logger = LoggerFactory.getLogger(RepositoryPermissionProvider.class); private static final String REPOSITORY_PERMISSION_DESCRIPTOR = "META-INF/scm/repository-permissions.xml"; private final ConfigurationEntryStoreFactory storeFactory; private final AvailableRepositoryPermissions availablePermissions; @Inject - public RepositoryPermissions(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader) { + public RepositoryPermissionProvider(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader) { this.storeFactory = storeFactory; this.availablePermissions = readAvailablePermissions(pluginLoader); } @@ -57,7 +57,7 @@ public class RepositoryPermissions { while (descriptorEnum.hasMoreElements()) { URL descriptorUrl = descriptorEnum.nextElement(); - logger.debug("read permission descriptor from {}", descriptorUrl); + logger.debug("read repository permission descriptor from {}", descriptorUrl); RepositoryPermissionsRoot repositoryPermissionsRoot = parsePermissionDescriptor(context, descriptorUrl); availableVerbs.addAll(repositoryPermissionsRoot.verbs.verbs); @@ -79,7 +79,8 @@ public class RepositoryPermissions { RepositoryPermissionsRoot descriptorWrapper = (RepositoryPermissionsRoot) context.createUnmarshaller().unmarshal( descriptorUrl); - logger.trace("permissions from {}: {}", descriptorUrl, descriptorWrapper); + 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); diff --git a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml index 89d47e156f..14412847a6 100644 --- a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml +++ b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml @@ -1,7 +1,14 @@ - abc - xyz + read + modify + delete + delete + healthCheck + pull + push + permissionRead + permissionWrite diff --git a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java new file mode 100644 index 0000000000..f1aa62f5b1 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java @@ -0,0 +1,62 @@ +package sonia.scm.security; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.repository.RepositoryPermissions; +import sonia.scm.store.ConfigurationEntryStoreFactory; +import sonia.scm.util.ClassLoaders; + +import java.lang.reflect.Field; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class RepositoryPermissionProviderTest { + + private RepositoryPermissionProvider repositoryPermissionProvider; + private String[] allVerbsFromRepositoryClass; + + + @BeforeEach + void init() { + PluginLoader pluginLoader = mock(PluginLoader.class); + when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class)); + ConfigurationEntryStoreFactory configurationEntryStoreFactory = mock(ConfigurationEntryStoreFactory.class); + repositoryPermissionProvider = new RepositoryPermissionProvider(configurationEntryStoreFactory, pluginLoader); + allVerbsFromRepositoryClass = Arrays.stream(RepositoryPermissions.class.getDeclaredFields()) + .filter(field -> field.getName().startsWith("ACTION_")) + .map(this::getString) + .filter(verb -> !"create".equals(verb)) + .toArray(String[]::new); + } + + @Test + void shouldReadAvailableRoles() { + assertThat(repositoryPermissionProvider.availableRoles()).isNotEmpty(); + assertThat(repositoryPermissionProvider.availableRoles()).allSatisfy(this::eitherStarOrOnlyAvailableVerbs); + } + + private void eitherStarOrOnlyAvailableVerbs(RepositoryPermissionProvider.RoleDescriptor role) { + if (!role.getVerbs().contains("*") || role.getVerbs().size() > 1) { + assertThat(role.getVerbs()).isSubsetOf(allVerbsFromRepositoryClass); + } + } + + @Test + void shouldReadAvailableVerbsFromRepository() { + assertThat(repositoryPermissionProvider.availableVerbs()).contains(allVerbsFromRepositoryClass); + } + + private String getString(Field field) { + try { + return (String) field.get(null); + } catch (IllegalAccessException e) { + fail(e); + return null; + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionsTest.java b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionsTest.java deleted file mode 100644 index 433e91e64c..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionsTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package sonia.scm.security; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import sonia.scm.plugin.PluginLoader; -import sonia.scm.store.ConfigurationEntryStoreFactory; -import sonia.scm.util.ClassLoaders; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class RepositoryPermissionsTest { - - private RepositoryPermissions repositoryPermissions; - - @BeforeEach - void init() { - PluginLoader pluginLoader = mock(PluginLoader.class); - when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class)); - ConfigurationEntryStoreFactory configurationEntryStoreFactory = mock(ConfigurationEntryStoreFactory.class); - repositoryPermissions = new RepositoryPermissions(configurationEntryStoreFactory, pluginLoader); - } - - @Test - void shouldReadAvailableRoles() { - Assertions.assertThat(repositoryPermissions.availableRoles()).isNotEmpty().noneMatch(r -> r.getVerbs().isEmpty()); - System.out.println(repositoryPermissions.availableRoles()); - } - - @Test - void shouldReadAvailableVerbs() { - Assertions.assertThat(repositoryPermissions.availableVerbs()).isNotEmpty(); - System.out.println(repositoryPermissions.availableVerbs()); - } -} From 69b64948a0a3aadfca70f7139cc14b03ce69335d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 12:33:34 +0100 Subject: [PATCH 15/48] Remove duplicates --- .../RepositoryPermissionProvider.java | 34 +++++++-------- .../sonia/scm/security/RepositoryRole.java | 42 +++++++++++++++++++ .../RepositoryPermissionProviderTest.java | 4 +- 3 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/security/RepositoryRole.java diff --git a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java index f02c31b0ed..d11da777e3 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java +++ b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java @@ -4,7 +4,6 @@ import com.google.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.plugin.PluginLoader; -import sonia.scm.store.ConfigurationEntryStoreFactory; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; @@ -16,30 +15,33 @@ import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Enumeration; +import java.util.HashSet; 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 ConfigurationEntryStoreFactory storeFactory; - private final AvailableRepositoryPermissions availablePermissions; + private final Collection availableVerbs; + private final Collection availableRoles; @Inject - public RepositoryPermissionProvider(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader) { - this.storeFactory = storeFactory; - this.availablePermissions = readAvailablePermissions(pluginLoader); + public RepositoryPermissionProvider(PluginLoader pluginLoader) { + AvailableRepositoryPermissions availablePermissions = readAvailablePermissions(pluginLoader); + this.availableVerbs = unmodifiableCollection(new HashSet<>(availablePermissions.availableVerbs)); + this.availableRoles = unmodifiableCollection(new HashSet<>(availablePermissions.availableRoles.stream().map(r -> new RepositoryRole(r.name, r.verbs.verbs)).collect(Collectors.toList()))); } public Collection availableVerbs() { - return availablePermissions.availableVerbs; + return availableVerbs; } - public Collection availableRoles() { - return availablePermissions.availableRoles; + public Collection availableRoles() { + return availableRoles; } private static AvailableRepositoryPermissions readAvailablePermissions(PluginLoader pluginLoader) { @@ -93,8 +95,8 @@ public class RepositoryPermissionProvider { private final Collection availableRoles; private AvailableRepositoryPermissions(Collection availableVerbs, Collection availableRoles) { - this.availableVerbs = Collections.unmodifiableCollection(availableVerbs); - this.availableRoles = Collections.unmodifiableCollection(availableRoles); + this.availableVerbs = unmodifiableCollection(availableVerbs); + this.availableRoles = unmodifiableCollection(availableRoles); } } @@ -124,13 +126,5 @@ public class RepositoryPermissionProvider { private String name; @XmlElement(name = "verbs") private VerbListDescriptor verbs = new VerbListDescriptor(); - - public Collection getVerbs() { - return Collections.unmodifiableCollection(verbs.verbs); - } - - public String toString() { - return "Role " + name + " (" + verbs.verbs.stream().collect(Collectors.joining(", ")) + ")"; - } } } diff --git a/scm-webapp/src/main/java/sonia/scm/security/RepositoryRole.java b/scm-webapp/src/main/java/sonia/scm/security/RepositoryRole.java new file mode 100644 index 0000000000..12170e3cf4 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/RepositoryRole.java @@ -0,0 +1,42 @@ +package sonia.scm.security; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +public class RepositoryRole { + + private final String name; + private final Collection verbs; + + public RepositoryRole(String name, Collection verbs) { + this.name = name; + this.verbs = verbs; + } + + public String getName() { + return name; + } + + public Collection 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) && + verbs.equals(that.verbs); + } + + @Override + public int hashCode() { + return Objects.hash(name, verbs); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java index f1aa62f5b1..487d8b71c8 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java @@ -26,7 +26,7 @@ class RepositoryPermissionProviderTest { PluginLoader pluginLoader = mock(PluginLoader.class); when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class)); ConfigurationEntryStoreFactory configurationEntryStoreFactory = mock(ConfigurationEntryStoreFactory.class); - repositoryPermissionProvider = new RepositoryPermissionProvider(configurationEntryStoreFactory, pluginLoader); + repositoryPermissionProvider = new RepositoryPermissionProvider(pluginLoader); allVerbsFromRepositoryClass = Arrays.stream(RepositoryPermissions.class.getDeclaredFields()) .filter(field -> field.getName().startsWith("ACTION_")) .map(this::getString) @@ -40,7 +40,7 @@ class RepositoryPermissionProviderTest { assertThat(repositoryPermissionProvider.availableRoles()).allSatisfy(this::eitherStarOrOnlyAvailableVerbs); } - private void eitherStarOrOnlyAvailableVerbs(RepositoryPermissionProvider.RoleDescriptor role) { + private void eitherStarOrOnlyAvailableVerbs(RepositoryRole role) { if (!role.getVerbs().contains("*") || role.getVerbs().size() > 1) { assertThat(role.getVerbs()).isSubsetOf(allVerbsFromRepositoryClass); } From 1bd0bbc7a32cddd67f869fa41e3ac3a227ce93ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 12:46:08 +0100 Subject: [PATCH 16/48] Better rest --- .../main/java/sonia/scm/web/VndMediaType.java | 1 + .../AvailableRepositoryPermissionsDto.java | 31 +++++++++++++++++++ .../RepositoryPermissionResource.java | 27 ++++++---------- .../scm/api/v2/resources/ResourceLinks.java | 22 +++++++++++-- .../META-INF/scm/repository-permissions.xml | 1 + .../api/v2/resources/ResourceLinksMock.java | 1 + .../RepositoryPermissionProviderTest.java | 10 ++---- 7 files changed, 66 insertions(+), 27 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailableRepositoryPermissionsDto.java diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 0836f177ee..19859b876b 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -33,6 +33,7 @@ public class VndMediaType { public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX; public static final String BRANCH_COLLECTION = PREFIX + "branchCollection" + SUFFIX; public static final String CONFIG = PREFIX + "config" + SUFFIX; + public static final String REPOSITORY_PERMISSION_COLLECTION = PREFIX + "repositoryPermissionCollection" + SUFFIX; public static final String REPOSITORY_TYPE_COLLECTION = PREFIX + "repositoryTypeCollection" + SUFFIX; public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX; public static final String UI_PLUGIN = PREFIX + "uiPlugin" + SUFFIX; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailableRepositoryPermissionsDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailableRepositoryPermissionsDto.java new file mode 100644 index 0000000000..60203b565b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AvailableRepositoryPermissionsDto.java @@ -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 availableVerbs; + private final Collection availableRoles; + + public AvailableRepositoryPermissionsDto(Collection availableVerbs, Collection availableRoles) { + this.availableVerbs = availableVerbs; + this.availableRoles = availableRoles; + } + + public Collection getAvailableVerbs() { + return availableVerbs; + } + + public Collection 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); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionResource.java index 2cfbf30de6..e5734085ca 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionResource.java @@ -2,6 +2,7 @@ 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; @@ -9,7 +10,6 @@ import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import java.util.Collection; /** * RESTful Web Service Resource to get available repository types. @@ -20,31 +20,24 @@ public class RepositoryPermissionResource { static final String PATH = "v2/repositoryPermissions/"; private final RepositoryPermissionProvider repositoryPermissionProvider; + private final ResourceLinks resourceLinks; @Inject - public RepositoryPermissionResource(RepositoryPermissionProvider repositoryPermissionProvider) { + public RepositoryPermissionResource(RepositoryPermissionProvider repositoryPermissionProvider, ResourceLinks resourceLinks) { this.repositoryPermissionProvider = repositoryPermissionProvider; + this.resourceLinks = resourceLinks; } @GET - @Path("verbs") + @Path("") @StatusCodes({ @ResponseCode(code = 200, condition = "success"), @ResponseCode(code = 500, condition = "internal server error") }) - @Produces(VndMediaType.REPOSITORY_TYPE_COLLECTION) - public Collection getRepositoryPermissionVerbs() { - return repositoryPermissionProvider.availableVerbs(); - } - - @GET - @Path("roles") - @StatusCodes({ - @ResponseCode(code = 200, condition = "success"), - @ResponseCode(code = 500, condition = "internal server error") - }) - @Produces(VndMediaType.REPOSITORY_TYPE_COLLECTION) - public Collection getRepositoryRoles() { - return repositoryPermissionProvider.availableRoles(); + @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; } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index c7369f7cd0..644b3adc3f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -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(); } } } diff --git a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml index 14412847a6..acbe0a8a82 100644 --- a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml +++ b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml @@ -9,6 +9,7 @@ push permissionRead permissionWrite + * diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index 655d00fc10..8714496e1a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -42,6 +42,7 @@ public class ResourceLinksMock { when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(uriInfo)); when(resourceLinks.merge()).thenReturn(new ResourceLinks.MergeLinks(uriInfo)); when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(uriInfo)); + when(resourceLinks.availableRepositoryPermissions()).thenReturn(new ResourceLinks.AvailableRepositoryPermissionLinks(uriInfo)); return resourceLinks; } diff --git a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java index 487d8b71c8..b5998084f8 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionProviderTest.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sonia.scm.plugin.PluginLoader; import sonia.scm.repository.RepositoryPermissions; -import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.util.ClassLoaders; import java.lang.reflect.Field; @@ -25,7 +24,6 @@ class RepositoryPermissionProviderTest { void init() { PluginLoader pluginLoader = mock(PluginLoader.class); when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class)); - ConfigurationEntryStoreFactory configurationEntryStoreFactory = mock(ConfigurationEntryStoreFactory.class); repositoryPermissionProvider = new RepositoryPermissionProvider(pluginLoader); allVerbsFromRepositoryClass = Arrays.stream(RepositoryPermissions.class.getDeclaredFields()) .filter(field -> field.getName().startsWith("ACTION_")) @@ -37,13 +35,11 @@ class RepositoryPermissionProviderTest { @Test void shouldReadAvailableRoles() { assertThat(repositoryPermissionProvider.availableRoles()).isNotEmpty(); - assertThat(repositoryPermissionProvider.availableRoles()).allSatisfy(this::eitherStarOrOnlyAvailableVerbs); + assertThat(repositoryPermissionProvider.availableRoles()).allSatisfy(this::containsOnlyAvailableVerbs); } - private void eitherStarOrOnlyAvailableVerbs(RepositoryRole role) { - if (!role.getVerbs().contains("*") || role.getVerbs().size() > 1) { - assertThat(role.getVerbs()).isSubsetOf(allVerbsFromRepositoryClass); - } + private void containsOnlyAvailableVerbs(RepositoryRole role) { + assertThat(role.getVerbs()).isSubsetOf(repositoryPermissionProvider.availableVerbs()); } @Test From 452977c5a977d45290bae8668b1037d9ce9cb18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 13:01:14 +0100 Subject: [PATCH 17/48] Fix unit test --- scm-core/src/test/resources/sonia/scm/shiro.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-core/src/test/resources/sonia/scm/shiro.ini b/scm-core/src/test/resources/sonia/scm/shiro.ini index fbdd35ba50..fda268ec83 100644 --- a/scm-core/src/test/resources/sonia/scm/shiro.ini +++ b/scm-core/src/test/resources/sonia/scm/shiro.ini @@ -8,5 +8,5 @@ unpriv = secret [roles] admin = * user = something:* -repo_read = "repository:read:1" -repo_write = "repository:push:1" +repo_read = "repository:read,pull:1" +repo_write = "repository:read,write,pull,push:1" From c33e7713d58791c7ae028d8dcbdd91678a75e588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 13:05:22 +0100 Subject: [PATCH 18/48] Fix integration test --- scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java index c431e9c09a..8cdb162740 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java @@ -187,7 +187,7 @@ public class GitLfsITCase { IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl)) .accept("*/*") .type(VndMediaType.REPOSITORY_PERMISSION) - .post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"verbs\":[\"read\"]}"); + .post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"verbs\":[\"read\",\"pull\"]}"); // upload data as admin String data = UUID.randomUUID().toString(); From 9898cd372123008f6502b061c70c2e0b6bad9abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 15:00:48 +0100 Subject: [PATCH 19/48] Fix authorization events --- .../scm/repository/RepositoryPermission.java | 11 +- .../repository/RepositoryPermissionTest.java | 49 ++++++++ .../AuthorizationChangedEventProducer.java | 4 +- ...AuthorizationChangedEventProducerTest.java | 108 ++++++++++-------- 4 files changed, 121 insertions(+), 51 deletions(-) create mode 100644 scm-core/src/test/java/sonia/scm/repository/RepositoryPermissionTest.java diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java index 97872f29eb..e83eaca66c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java @@ -37,6 +37,7 @@ package sonia.scm.repository; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; +import org.apache.commons.collections.CollectionUtils; import sonia.scm.security.PermissionObject; import javax.xml.bind.annotation.XmlAccessType; @@ -45,8 +46,10 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.Collection; +import java.util.HashSet; import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableCollection; //~--- JDK imports ------------------------------------------------------------ @@ -76,7 +79,7 @@ public class RepositoryPermission implements PermissionObject, Serializable public RepositoryPermission(String name, Collection verbs, boolean groupPermission) { this.name = name; - this.verbs = verbs; + this.verbs = unmodifiableCollection(new HashSet<>(verbs)); this.groupPermission = groupPermission; } @@ -106,7 +109,7 @@ public class RepositoryPermission implements PermissionObject, Serializable final RepositoryPermission other = (RepositoryPermission) obj; return Objects.equal(name, other.name) - && Objects.equal(verbs, other.verbs) + && CollectionUtils.isEqualCollection(verbs, other.verbs) && Objects.equal(groupPermission, other.groupPermission); } @@ -119,7 +122,9 @@ public class RepositoryPermission implements PermissionObject, Serializable @Override public int hashCode() { - return Objects.hashCode(name, verbs, groupPermission); + // Normally we do not have a log of repository permissions having the same size of verbs, but different content. + // Therefore we do not use the verbs themselves for the hash code but only the number of verbs. + return Objects.hashCode(name, verbs.size(), groupPermission); } diff --git a/scm-core/src/test/java/sonia/scm/repository/RepositoryPermissionTest.java b/scm-core/src/test/java/sonia/scm/repository/RepositoryPermissionTest.java new file mode 100644 index 0000000000..2e9383b2e2 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/repository/RepositoryPermissionTest.java @@ -0,0 +1,49 @@ +package sonia.scm.repository; + +import org.junit.jupiter.api.Test; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +class RepositoryPermissionTest { + + @Test + void shouldBeEqualWithSameVerbs() { + RepositoryPermission permission1 = new RepositoryPermission("name", asList("one", "two"), false); + RepositoryPermission permission2 = new RepositoryPermission("name", asList("two", "one"), false); + + assertThat(permission1).isEqualTo(permission2); + } + + @Test + void shouldHaveSameHashCodeWithSameVerbs() { + long hash1 = new RepositoryPermission("name", asList("one", "two"), false).hashCode(); + long hash2 = new RepositoryPermission("name", asList("two", "one"), false).hashCode(); + + assertThat(hash1).isEqualTo(hash2); + } + + @Test + void shouldNotBeEqualWithSameVerbs() { + RepositoryPermission permission1 = new RepositoryPermission("name", asList("one", "two"), false); + RepositoryPermission permission2 = new RepositoryPermission("name", asList("three", "one"), false); + + assertThat(permission1).isNotEqualTo(permission2); + } + + @Test + void shouldNotBeEqualWithDifferentType() { + RepositoryPermission permission1 = new RepositoryPermission("name", asList("one"), false); + RepositoryPermission permission2 = new RepositoryPermission("name", asList("one"), true); + + assertThat(permission1).isNotEqualTo(permission2); + } + + @Test + void shouldNotBeEqualWithDifferentName() { + RepositoryPermission permission1 = new RepositoryPermission("name1", asList("one"), false); + RepositoryPermission permission2 = new RepositoryPermission("name2", asList("one"), false); + + assertThat(permission1).isNotEqualTo(permission2); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java index c3e7dc4f0c..0586db2bb3 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java @@ -167,9 +167,7 @@ public class AuthorizationChangedEventProducer { private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) { return repository.isArchived() != beforeModification.isArchived() || repository.isPublicReadable() != beforeModification.isPublicReadable() - // TODO RP -// || !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions())) - ; + || !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions())); } private void fireEventForEveryUser() { diff --git a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java index de31aa1298..59b6951025 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java @@ -31,21 +31,31 @@ package sonia.scm.security; -import org.junit.Test; -import static org.junit.Assert.*; +import com.google.common.collect.Lists; import org.junit.Before; +import org.junit.Test; import sonia.scm.HandlerEventType; import sonia.scm.group.Group; import sonia.scm.group.GroupEvent; import sonia.scm.group.GroupModificationEvent; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryEvent; +import sonia.scm.repository.RepositoryModificationEvent; +import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.RepositoryTestData; import sonia.scm.user.User; import sonia.scm.user.UserEvent; import sonia.scm.user.UserModificationEvent; import sonia.scm.user.UserTestData; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + /** * Unit tests for {@link AuthorizationChangedEventProducer}. * @@ -83,7 +93,12 @@ public class AuthorizationChangedEventProducerTest { assertTrue(producer.event.isEveryUserAffected()); assertEquals(username, producer.event.getNameOfAffectedUser()); } - + + private void assertGlobalEventIsFired(){ + assertNotNull(producer.event); + assertFalse(producer.event.isEveryUserAffected()); + } + /** * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.user.UserEvent)} with modified user. */ @@ -123,11 +138,6 @@ public class AuthorizationChangedEventProducerTest { assertGlobalEventIsFired(); } - private void assertGlobalEventIsFired(){ - assertNotNull(producer.event); - assertFalse(producer.event.isEveryUserAffected()); - } - /** * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.group.GroupEvent)} with modified groups. */ @@ -168,43 +178,51 @@ public class AuthorizationChangedEventProducerTest { @Test public void testOnRepositoryModificationEvent() { - // TODO RP -// Repository repositoryModified = RepositoryTestData.createHeartOfGold(); -// repositoryModified.setName("test123"); -// repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test"))); -// -// Repository repository = RepositoryTestData.createHeartOfGold(); -// repository.setPermissions(Lists.newArrayList(new RepositoryPermission("test"))); -// -// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.BEFORE_CREATE, repositoryModified, repository)); -// assertEventIsNotFired(); -// -// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); -// assertEventIsNotFired(); -// -// repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test"))); -// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); -// assertEventIsNotFired(); -// -// repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test123"))); -// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); -// assertGlobalEventIsFired(); -// -// resetStoredEvent(); -// -// repositoryModified.setPermissions( -// Lists.newArrayList(new RepositoryPermission("test", PermissionType.READ, true)) -// ); -// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); -// assertGlobalEventIsFired(); -// -// resetStoredEvent(); -// -// repositoryModified.setPermissions( -// Lists.newArrayList(new RepositoryPermission("test", PermissionType.WRITE)) -// ); -// producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); -// assertGlobalEventIsFired(); + Repository repositoryModified = RepositoryTestData.createHeartOfGold(); + repositoryModified.setName("test123"); + repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test", singletonList("read"), false))); + + Repository repository = RepositoryTestData.createHeartOfGold(); + repository.setPermissions(Lists.newArrayList(new RepositoryPermission("test", singletonList("read"), false))); + + producer.onEvent(new RepositoryModificationEvent(HandlerEventType.BEFORE_CREATE, repositoryModified, repository)); + assertEventIsNotFired(); + + producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); + assertEventIsNotFired(); + + repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test", singletonList("read"), false))); + producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); + assertEventIsNotFired(); + + repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test123", singletonList("read"), false))); + producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); + assertGlobalEventIsFired(); + + resetStoredEvent(); + + repositoryModified.setPermissions( + Lists.newArrayList(new RepositoryPermission("test", singletonList("read"), true)) + ); + producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); + assertGlobalEventIsFired(); + + resetStoredEvent(); + + repositoryModified.setPermissions( + Lists.newArrayList(new RepositoryPermission("test", asList("read", "write"), false)) + ); + producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); + assertGlobalEventIsFired(); + + resetStoredEvent(); + repository.setPermissions(Lists.newArrayList(new RepositoryPermission("test", asList("read", "write"), false))); + + repositoryModified.setPermissions( + Lists.newArrayList(new RepositoryPermission("test", asList("write", "read"), false)) + ); + producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository)); + assertEventIsNotFired(); } private void resetStoredEvent(){ From f52edf4dd1cf5f5f52c50c0f1c97eaa5fea9b015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 15:03:40 +0100 Subject: [PATCH 20/48] Add link to available repository permissions to index --- .../main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java index 6377f21163..3eff661385 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -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())); } From 42dcaec71afa710628f98ff12a1c471a18feecfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 23 Jan 2019 15:40:08 +0100 Subject: [PATCH 21/48] Keep order of permissions --- .../java/sonia/scm/repository/RepositoryPermission.java | 4 ++-- .../sonia/scm/security/RepositoryPermissionProvider.java | 6 +++--- .../main/resources/META-INF/scm/repository-permissions.xml | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java index e83eaca66c..9e132ef93c 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPermission.java @@ -46,7 +46,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.util.Collection; -import java.util.HashSet; +import java.util.LinkedHashSet; import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableCollection; @@ -79,7 +79,7 @@ public class RepositoryPermission implements PermissionObject, Serializable public RepositoryPermission(String name, Collection verbs, boolean groupPermission) { this.name = name; - this.verbs = unmodifiableCollection(new HashSet<>(verbs)); + this.verbs = unmodifiableCollection(new LinkedHashSet<>(verbs)); this.groupPermission = groupPermission; } diff --git a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java index d11da777e3..0a508753bd 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java +++ b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionProvider.java @@ -16,7 +16,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.stream.Collectors; @@ -32,8 +32,8 @@ public class RepositoryPermissionProvider { @Inject public RepositoryPermissionProvider(PluginLoader pluginLoader) { AvailableRepositoryPermissions availablePermissions = readAvailablePermissions(pluginLoader); - this.availableVerbs = unmodifiableCollection(new HashSet<>(availablePermissions.availableVerbs)); - this.availableRoles = unmodifiableCollection(new HashSet<>(availablePermissions.availableRoles.stream().map(r -> new RepositoryRole(r.name, r.verbs.verbs)).collect(Collectors.toList()))); + 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 availableVerbs() { diff --git a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml index acbe0a8a82..0266e4e22e 100644 --- a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml +++ b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml @@ -3,12 +3,11 @@ read modify delete - delete - healthCheck pull push permissionRead permissionWrite + healthCheck * From 5d3cbff461c475283e24bb46af0a288749f062b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 24 Jan 2019 08:05:28 +0100 Subject: [PATCH 22/48] Load available repository permissions --- .../src/AvailableRepositoryPermissions.js | 11 + .../ui-types/src/RepositoryPermissions.js | 2 +- .../packages/ui-types/src/index.js | 2 + .../permissions/containers/Permissions.js | 462 +++++++++--------- .../repos/permissions/modules/permissions.js | 95 +++- 5 files changed, 344 insertions(+), 228 deletions(-) create mode 100644 scm-ui-components/packages/ui-types/src/AvailableRepositoryPermissions.js diff --git a/scm-ui-components/packages/ui-types/src/AvailableRepositoryPermissions.js b/scm-ui-components/packages/ui-types/src/AvailableRepositoryPermissions.js new file mode 100644 index 0000000000..ab7e8d82e4 --- /dev/null +++ b/scm-ui-components/packages/ui-types/src/AvailableRepositoryPermissions.js @@ -0,0 +1,11 @@ +// @flow + +export type RepositoryRole = { + name: string, + verbs: string[] +}; + +export type AvailableRepositoryPermissions = { + availableVerbs: string[], + availableRoles: RepositoryRole[] +}; diff --git a/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js b/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js index 4352c21da6..ed3c925283 100644 --- a/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js +++ b/scm-ui-components/packages/ui-types/src/RepositoryPermissions.js @@ -7,7 +7,7 @@ export type Permission = PermissionCreateEntry & { export type PermissionCreateEntry = { name: string, - type: string, + verbs: string[], groupPermission: boolean } diff --git a/scm-ui-components/packages/ui-types/src/index.js b/scm-ui-components/packages/ui-types/src/index.js index cf739f747d..f7b375ac98 100644 --- a/scm-ui-components/packages/ui-types/src/index.js +++ b/scm-ui-components/packages/ui-types/src/index.js @@ -24,3 +24,5 @@ export type { Permission, PermissionCreateEntry, PermissionCollection } from "./ export type { SubRepository, File } from "./Sources"; export type { SelectValue, AutocompleteObject } from "./Autocomplete"; + +export type { AvailableRepositoryPermissions, RepositoryRole } from "./AvailableRepositoryPermissions"; diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js index 48f4e585f7..4d17f65e16 100644 --- a/scm-ui/src/repos/permissions/containers/Permissions.js +++ b/scm-ui/src/repos/permissions/containers/Permissions.js @@ -1,225 +1,237 @@ -//@flow -import React from "react"; -import { connect } from "react-redux"; -import { translate } from "react-i18next"; -import { - fetchPermissions, - getFetchPermissionsFailure, - isFetchPermissionsPending, - getPermissionsOfRepo, - hasCreatePermission, - createPermission, - isCreatePermissionPending, - getCreatePermissionFailure, - createPermissionReset, - getDeletePermissionsFailure, - getModifyPermissionsFailure, - modifyPermissionReset, - deletePermissionReset -} from "../modules/permissions"; -import { Loading, ErrorPage } from "@scm-manager/ui-components"; -import type { - Permission, - PermissionCollection, - PermissionCreateEntry -} from "@scm-manager/ui-types"; -import SinglePermission from "./SinglePermission"; -import CreatePermissionForm from "../components/CreatePermissionForm"; -import type { History } from "history"; -import { getPermissionsLink } from "../../modules/repos"; -import { - getGroupAutoCompleteLink, - getUserAutoCompleteLink -} from "../../../modules/indexResource"; - -type Props = { - namespace: string, - repoName: string, - loading: boolean, - error: Error, - permissions: PermissionCollection, - hasPermissionToCreate: boolean, - loadingCreatePermission: boolean, - permissionsLink: string, - groupAutoCompleteLink: string, - userAutoCompleteLink: string, - - //dispatch functions - fetchPermissions: (link: string, namespace: string, repoName: string) => void, - createPermission: ( - link: string, - permission: PermissionCreateEntry, - namespace: string, - repoName: string, - callback?: () => void - ) => void, - createPermissionReset: (string, string) => void, - modifyPermissionReset: (string, string) => void, - deletePermissionReset: (string, string) => void, - // context props - t: string => string, - match: any, - history: History -}; - -class Permissions extends React.Component { - componentDidMount() { - const { - fetchPermissions, - namespace, - repoName, - modifyPermissionReset, - createPermissionReset, - deletePermissionReset, - permissionsLink - } = this.props; - - createPermissionReset(namespace, repoName); - modifyPermissionReset(namespace, repoName); - deletePermissionReset(namespace, repoName); - fetchPermissions(permissionsLink, namespace, repoName); - } - - createPermission = (permission: Permission) => { - this.props.createPermission( - this.props.permissionsLink, - permission, - this.props.namespace, - this.props.repoName - ); - }; - - render() { - const { - loading, - error, - permissions, - t, - namespace, - repoName, - loadingCreatePermission, - hasPermissionToCreate, - userAutoCompleteLink, - groupAutoCompleteLink - } = this.props; - if (error) { - return ( - - ); - } - - if (loading || !permissions) { - return ; - } - - const createPermissionForm = hasPermissionToCreate ? ( - this.createPermission(permission)} - loading={loadingCreatePermission} - currentPermissions={permissions} - userAutoCompleteLink={userAutoCompleteLink} - groupAutoCompleteLink={groupAutoCompleteLink} - /> - ) : null; - - return ( -
- - - - - - - - - - {permissions.map(permission => { - return ( - - ); - })} - -
{t("permission.name")} - {t("permission.group-permission")} - {t("permission.type")} -
- {createPermissionForm} -
- ); - } -} - -const mapStateToProps = (state, ownProps) => { - const namespace = ownProps.namespace; - const repoName = ownProps.repoName; - const error = - getFetchPermissionsFailure(state, namespace, repoName) || - getCreatePermissionFailure(state, namespace, repoName) || - getDeletePermissionsFailure(state, namespace, repoName) || - getModifyPermissionsFailure(state, namespace, repoName); - const loading = isFetchPermissionsPending(state, namespace, repoName); - const permissions = getPermissionsOfRepo(state, namespace, repoName); - const loadingCreatePermission = isCreatePermissionPending( - state, - namespace, - repoName - ); - const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName); - const permissionsLink = getPermissionsLink(state, namespace, repoName); - const groupAutoCompleteLink = getGroupAutoCompleteLink(state); - const userAutoCompleteLink = getUserAutoCompleteLink(state); - return { - namespace, - repoName, - error, - loading, - permissions, - hasPermissionToCreate, - loadingCreatePermission, - permissionsLink, - groupAutoCompleteLink, - userAutoCompleteLink - }; -}; - -const mapDispatchToProps = dispatch => { - return { - fetchPermissions: (link: string, namespace: string, repoName: string) => { - dispatch(fetchPermissions(link, namespace, repoName)); - }, - createPermission: ( - link: string, - permission: PermissionCreateEntry, - namespace: string, - repoName: string, - callback?: () => void - ) => { - dispatch( - createPermission(link, permission, namespace, repoName, callback) - ); - }, - createPermissionReset: (namespace: string, repoName: string) => { - dispatch(createPermissionReset(namespace, repoName)); - }, - modifyPermissionReset: (namespace: string, repoName: string) => { - dispatch(modifyPermissionReset(namespace, repoName)); - }, - deletePermissionReset: (namespace: string, repoName: string) => { - dispatch(deletePermissionReset(namespace, repoName)); - } - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(translate("repos")(Permissions)); +//@flow +import React from "react"; +import { connect } from "react-redux"; +import { translate } from "react-i18next"; +import { + fetchAvailablePermissionsIfNeeded, + fetchPermissions, + getFetchAvailablePermissionsFailure, + getFetchPermissionsFailure, + isFetchAvailablePermissionsPending, + isFetchPermissionsPending, + getPermissionsOfRepo, + hasCreatePermission, + createPermission, + isCreatePermissionPending, + getCreatePermissionFailure, + createPermissionReset, + getDeletePermissionsFailure, + getModifyPermissionsFailure, + modifyPermissionReset, + deletePermissionReset +} from "../modules/permissions"; +import { Loading, ErrorPage } from "@scm-manager/ui-components"; +import type { + Permission, + PermissionCollection, + PermissionCreateEntry +} from "@scm-manager/ui-types"; +import SinglePermission from "./SinglePermission"; +import CreatePermissionForm from "../components/CreatePermissionForm"; +import type { History } from "history"; +import { getPermissionsLink } from "../../modules/repos"; +import { + getGroupAutoCompleteLink, + getUserAutoCompleteLink +} from "../../../modules/indexResource"; + +type Props = { + namespace: string, + repoName: string, + loading: boolean, + error: Error, + permissions: PermissionCollection, + hasPermissionToCreate: boolean, + loadingCreatePermission: boolean, + permissionsLink: string, + groupAutoCompleteLink: string, + userAutoCompleteLink: string, + + //dispatch functions + fetchAvailablePermissionsIfNeeded: () => void, + fetchPermissions: (link: string, namespace: string, repoName: string) => void, + createPermission: ( + link: string, + permission: PermissionCreateEntry, + namespace: string, + repoName: string, + callback?: () => void + ) => void, + createPermissionReset: (string, string) => void, + modifyPermissionReset: (string, string) => void, + deletePermissionReset: (string, string) => void, + // context props + t: string => string, + match: any, + history: History +}; + +class Permissions extends React.Component { + componentDidMount() { + const { + fetchAvailablePermissionsIfNeeded, + fetchPermissions, + namespace, + repoName, + modifyPermissionReset, + createPermissionReset, + deletePermissionReset, + permissionsLink + } = this.props; + + createPermissionReset(namespace, repoName); + modifyPermissionReset(namespace, repoName); + deletePermissionReset(namespace, repoName); + fetchAvailablePermissionsIfNeeded(); + fetchPermissions(permissionsLink, namespace, repoName); + } + + createPermission = (permission: Permission) => { + this.props.createPermission( + this.props.permissionsLink, + permission, + this.props.namespace, + this.props.repoName + ); + }; + + render() { + const { + loading, + error, + permissions, + t, + namespace, + repoName, + loadingCreatePermission, + hasPermissionToCreate, + userAutoCompleteLink, + groupAutoCompleteLink + } = this.props; + if (error) { + return ( + + ); + } + + if (loading || !permissions) { + return ; + } + + const createPermissionForm = hasPermissionToCreate ? ( + this.createPermission(permission)} + loading={loadingCreatePermission} + currentPermissions={permissions} + userAutoCompleteLink={userAutoCompleteLink} + groupAutoCompleteLink={groupAutoCompleteLink} + /> + ) : null; + + return ( +
+ + + + + + + + + + {permissions.map(permission => { + return ( + + ); + })} + +
{t("permission.name")} + {t("permission.group-permission")} + {t("permission.type")} +
+ {createPermissionForm} +
+ ); + } +} + +const mapStateToProps = (state, ownProps) => { + const namespace = ownProps.namespace; + const repoName = ownProps.repoName; + const error = + getFetchPermissionsFailure(state, namespace, repoName) || + getCreatePermissionFailure(state, namespace, repoName) || + getDeletePermissionsFailure(state, namespace, repoName) || + getModifyPermissionsFailure(state, namespace, repoName) || + getFetchAvailablePermissionsFailure(state); + const loading = + isFetchPermissionsPending(state, namespace, repoName) || + isFetchAvailablePermissionsPending(state); + const permissions = getPermissionsOfRepo(state, namespace, repoName); + const loadingCreatePermission = isCreatePermissionPending( + state, + namespace, + repoName + ); + const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName); + const permissionsLink = getPermissionsLink(state, namespace, repoName); + const groupAutoCompleteLink = getGroupAutoCompleteLink(state); + const userAutoCompleteLink = getUserAutoCompleteLink(state); + return { + namespace, + repoName, + error, + loading, + permissions, + hasPermissionToCreate, + loadingCreatePermission, + permissionsLink, + groupAutoCompleteLink, + userAutoCompleteLink + }; +}; + +const mapDispatchToProps = dispatch => { + return { + fetchPermissions: (link: string, namespace: string, repoName: string) => { + dispatch(fetchPermissions(link, namespace, repoName)); + }, + fetchAvailablePermissionsIfNeeded: () => { + dispatch(fetchAvailablePermissionsIfNeeded()); + }, + createPermission: ( + link: string, + permission: PermissionCreateEntry, + namespace: string, + repoName: string, + callback?: () => void + ) => { + dispatch( + createPermission(link, permission, namespace, repoName, callback) + ); + }, + createPermissionReset: (namespace: string, repoName: string) => { + dispatch(createPermissionReset(namespace, repoName)); + }, + modifyPermissionReset: (namespace: string, repoName: string) => { + dispatch(modifyPermissionReset(namespace, repoName)); + }, + deletePermissionReset: (namespace: string, repoName: string) => { + dispatch(deletePermissionReset(namespace, repoName)); + } + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(translate("repos")(Permissions)); diff --git a/scm-ui/src/repos/permissions/modules/permissions.js b/scm-ui/src/repos/permissions/modules/permissions.js index 9f5330bbcd..74d0ea5bc7 100644 --- a/scm-ui/src/repos/permissions/modules/permissions.js +++ b/scm-ui/src/repos/permissions/modules/permissions.js @@ -4,6 +4,7 @@ import type { Action } from "@scm-manager/ui-components"; import { apiClient } from "@scm-manager/ui-components"; import * as types from "../../../modules/types"; import type { + AvailableRepositoryPermissions, Permission, PermissionCollection, PermissionCreateEntry @@ -11,7 +12,18 @@ import type { import { isPending } from "../../../modules/pending"; import { getFailure } from "../../../modules/failure"; import { Dispatch } from "redux"; +import { getLinks } from "../../../modules/indexResource"; +export const FETCH_AVAILABLE = "scm/permissions/FETCH_AVAILABLE"; +export const FETCH_AVAILABLE_PENDING = `${FETCH_AVAILABLE}_${ + types.PENDING_SUFFIX +}`; +export const FETCH_AVAILABLE_SUCCESS = `${FETCH_AVAILABLE}_${ + types.SUCCESS_SUFFIX +}`; +export const FETCH_AVAILABLE_FAILURE = `${FETCH_AVAILABLE}_${ + types.FAILURE_SUFFIX +}`; export const FETCH_PERMISSIONS = "scm/permissions/FETCH_PERMISSIONS"; export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${ types.PENDING_SUFFIX @@ -62,7 +74,71 @@ export const DELETE_PERMISSION_RESET = `${DELETE_PERMISSION}_${ types.RESET_SUFFIX }`; -const CONTENT_TYPE = "application/vnd.scmm-permission+json"; +const CONTENT_TYPE = "application/vnd.scmm-repositoryPermission+json"; + +// fetch available permissions + +export function fetchAvailablePermissionsIfNeeded() { + return function(dispatch: any, getState: () => Object) { + if (shouldFetchAvailablePermissions(getState())) { + return fetchAvailablePermissions(dispatch, getState); + } + }; +} + +export function fetchAvailablePermissions( + dispatch: any, + getState: () => Object +) { + dispatch(fetchAvailablePending()); + return apiClient + .get(getLinks(getState()).availableRepositoryPermissions.href) + .then(response => response.json()) + .then(available => { + dispatch(fetchAvailableSuccess(available)); + }) + .catch(err => { + dispatch(fetchAvailableFailure(err)); + }); +} + +export function shouldFetchAvailablePermissions(state: Object) { + if ( + isFetchAvailablePermissionsPending(state) || + getFetchAvailablePermissionsFailure(state) + ) { + return false; + } + return !state.available; +} + +export function fetchAvailablePending(): Action { + return { + type: FETCH_AVAILABLE_PENDING, + payload: {}, + itemId: "available" + }; +} + +export function fetchAvailableSuccess( + available: AvailableRepositoryPermissions +): Action { + return { + type: FETCH_AVAILABLE_SUCCESS, + payload: available, + itemId: "available" + }; +} + +export function fetchAvailableFailure(error: Error): Action { + return { + type: FETCH_AVAILABLE_FAILURE, + payload: { + error + }, + itemId: "available" + }; +} // fetch permissions @@ -368,6 +444,7 @@ export function deletePermissionReset(namespace: string, repoName: string) { itemId: namespace + "/" + repoName }; } + function deletePermissionFromState( oldPermissions: PermissionCollection, permission: Permission @@ -399,12 +476,17 @@ export default function reducer( return state; } switch (action.type) { + case FETCH_AVAILABLE_SUCCESS: + return { + ...state, + available: action.payload + }; case FETCH_PERMISSIONS_SUCCESS: return { ...state, [action.itemId]: { entries: action.payload._embedded.permissions, - createPermission: action.payload._links.create ? true : false + createPermission: !!action.payload._links.create } }; case MODIFY_PERMISSION_SUCCESS: @@ -463,6 +545,10 @@ export function getPermissionsOfRepo( } } +export function isFetchAvailablePermissionsPending(state: Object) { + return isPending(state, FETCH_AVAILABLE, "available"); +} + export function isFetchPermissionsPending( state: Object, namespace: string, @@ -471,6 +557,10 @@ export function isFetchPermissionsPending( return isPending(state, FETCH_PERMISSIONS, namespace + "/" + repoName); } +export function getFetchAvailablePermissionsFailure(state: Object) { + return getFailure(state, FETCH_AVAILABLE, "available"); +} + export function getFetchPermissionsFailure( state: Object, namespace: string, @@ -522,6 +612,7 @@ export function isCreatePermissionPending( ) { return isPending(state, CREATE_PERMISSION, namespace + "/" + repoName); } + export function getCreatePermissionFailure( state: Object, namespace: string, From 5b8518fbd9836d60d62094c876d5b4da1c33ce5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 24 Jan 2019 09:53:26 +0100 Subject: [PATCH 23/48] Load available permissions and find matching role --- .../permissions/components/TypeSelector.js | 93 ++-- .../permissions/containers/Permissions.js | 9 +- .../containers/SinglePermission.js | 397 ++++++++++-------- .../repos/permissions/modules/permissions.js | 6 + 4 files changed, 286 insertions(+), 219 deletions(-) diff --git a/scm-ui/src/repos/permissions/components/TypeSelector.js b/scm-ui/src/repos/permissions/components/TypeSelector.js index bec2c5b278..9945fa4def 100644 --- a/scm-ui/src/repos/permissions/components/TypeSelector.js +++ b/scm-ui/src/repos/permissions/components/TypeSelector.js @@ -1,42 +1,51 @@ -// @flow -import React from "react"; -import { translate } from "react-i18next"; -import { Select } from "@scm-manager/ui-components"; - -type Props = { - t: string => string, - handleTypeChange: string => void, - type: string, - label?: string, - helpText?: string, - loading?: boolean -}; - -class TypeSelector extends React.Component { - render() { - const { type, handleTypeChange, loading, label, helpText } = this.props; - const types = ["READ", "OWNER", "WRITE"]; - - return ( - + ); + } + + createSelectOptions(types: string[]) { + return types.map(type => { + return { + label: type, + value: type + }; + }); + } +} + +export default translate("repos")(TypeSelector); diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js index 4d17f65e16..830d71df66 100644 --- a/scm-ui/src/repos/permissions/containers/Permissions.js +++ b/scm-ui/src/repos/permissions/containers/Permissions.js @@ -6,6 +6,7 @@ import { fetchAvailablePermissionsIfNeeded, fetchPermissions, getFetchAvailablePermissionsFailure, + getAvailablePermissions, getFetchPermissionsFailure, isFetchAvailablePermissionsPending, isFetchPermissionsPending, @@ -22,6 +23,7 @@ import { } from "../modules/permissions"; import { Loading, ErrorPage } from "@scm-manager/ui-components"; import type { + AvailableRepositoryPermissions, Permission, PermissionCollection, PermissionCreateEntry @@ -36,6 +38,7 @@ import { } from "../../../modules/indexResource"; type Props = { + availablePermissions: AvailableRepositoryPermissions, namespace: string, repoName: string, loading: boolean, @@ -97,6 +100,7 @@ class Permissions extends React.Component { render() { const { + availablePermissions, loading, error, permissions, @@ -118,7 +122,7 @@ class Permissions extends React.Component { ); } - if (loading || !permissions) { + if (loading || !permissions || !availablePermissions) { return ; } @@ -149,6 +153,7 @@ class Permissions extends React.Component { {permissions.map(permission => { return ( { const permissionsLink = getPermissionsLink(state, namespace, repoName); const groupAutoCompleteLink = getGroupAutoCompleteLink(state); const userAutoCompleteLink = getUserAutoCompleteLink(state); + const availablePermissions = getAvailablePermissions(state); return { + availablePermissions, namespace, repoName, error, diff --git a/scm-ui/src/repos/permissions/containers/SinglePermission.js b/scm-ui/src/repos/permissions/containers/SinglePermission.js index 62380128be..1f260b4ae7 100644 --- a/scm-ui/src/repos/permissions/containers/SinglePermission.js +++ b/scm-ui/src/repos/permissions/containers/SinglePermission.js @@ -1,176 +1,221 @@ -// @flow -import React from "react"; -import type { Permission } from "@scm-manager/ui-types"; -import { translate } from "react-i18next"; -import { - modifyPermission, - isModifyPermissionPending, - deletePermission, - isDeletePermissionPending -} from "../modules/permissions"; -import { connect } from "react-redux"; -import type { History } from "history"; -import { Checkbox } from "@scm-manager/ui-components"; -import DeletePermissionButton from "../components/buttons/DeletePermissionButton"; -import TypeSelector from "../components/TypeSelector"; - -type Props = { - submitForm: Permission => void, - modifyPermission: (Permission, string, string) => void, - permission: Permission, - t: string => string, - namespace: string, - repoName: string, - match: any, - history: History, - loading: boolean, - deletePermission: (Permission, string, string) => void, - deleteLoading: boolean -}; - -type State = { - permission: Permission -}; - -class SinglePermission extends React.Component { - constructor(props: Props) { - super(props); - - this.state = { - permission: { - name: "", - type: "READ", - groupPermission: false, - _links: {} - } - }; - } - - componentDidMount() { - const { permission } = this.props; - if (permission) { - this.setState({ - permission: { - name: permission.name, - type: permission.type, - groupPermission: permission.groupPermission, - _links: permission._links - } - }); - } - } - - deletePermission = () => { - this.props.deletePermission( - this.props.permission, - this.props.namespace, - this.props.repoName - ); - }; - - render() { - const { permission } = this.state; - const { loading, namespace, repoName } = this.props; - const typeSelector = - this.props.permission._links && this.props.permission._links.update ? ( - - - - ) : ( - {permission.type} - ); - - return ( - - {permission.name} - - - - {typeSelector} - - - - - ); - } - - handleTypeChange = (type: string) => { - this.setState({ - permission: { - ...this.state.permission, - type: type - } - }); - this.modifyPermission(type); - }; - - modifyPermission = (type: string) => { - let permission = this.state.permission; - permission.type = type; - this.props.modifyPermission( - permission, - this.props.namespace, - this.props.repoName - ); - }; - - createSelectOptions(types: string[]) { - return types.map(type => { - return { - label: type, - value: type - }; - }); - } -} - -const mapStateToProps = (state, ownProps) => { - const permission = ownProps.permission; - const loading = isModifyPermissionPending( - state, - ownProps.namespace, - ownProps.repoName, - permission - ); - const deleteLoading = isDeletePermissionPending( - state, - ownProps.namespace, - ownProps.repoName, - permission - ); - - return { loading, deleteLoading }; -}; - -const mapDispatchToProps = dispatch => { - return { - modifyPermission: ( - permission: Permission, - namespace: string, - repoName: string - ) => { - dispatch(modifyPermission(permission, namespace, repoName)); - }, - deletePermission: ( - permission: Permission, - namespace: string, - repoName: string - ) => { - dispatch(deletePermission(permission, namespace, repoName)); - } - }; -}; -export default connect( - mapStateToProps, - mapDispatchToProps -)(translate("repos")(SinglePermission)); +// @flow +import React from "react"; +import type { + AvailableRepositoryPermissions, + Permission +} from "@scm-manager/ui-types"; +import { translate } from "react-i18next"; +import { + modifyPermission, + isModifyPermissionPending, + deletePermission, + isDeletePermissionPending +} from "../modules/permissions"; +import { connect } from "react-redux"; +import type { History } from "history"; +import { Checkbox } from "@scm-manager/ui-components"; +import DeletePermissionButton from "../components/buttons/DeletePermissionButton"; +import TypeSelector from "../components/TypeSelector"; + +type Props = { + availablePermissions: AvailableRepositoryPermissions, + submitForm: Permission => void, + modifyPermission: (Permission, string, string) => void, + permission: Permission, + t: string => string, + namespace: string, + repoName: string, + match: any, + history: History, + loading: boolean, + deletePermission: (Permission, string, string) => void, + deleteLoading: boolean +}; + +type State = { + role: string, + permission: Permission +}; + +class SinglePermission extends React.Component { + constructor(props: Props) { + super(props); + + const defaultPermission = props.availablePermissions.availableRoles + ? props.availablePermissions.availableRoles[0] + : {}; + + this.state = { + permission: { + name: "", + verbs: defaultPermission.verbs, + groupPermission: false, + _links: {} + }, + role: defaultPermission.name + }; + } + + componentDidMount() { + const { permission } = this.props; + + const matchingRole = this.findMatchingRoleName(); + + if (permission) { + this.setState({ + permission: { + name: permission.name, + verbs: permission.verbs, + groupPermission: permission.groupPermission, + _links: permission._links + }, + role: matchingRole + }); + } + } + + findMatchingRoleName = () => { + const { availablePermissions, permission } = this.props; + if (!permission) { + return ""; + } + const matchingRole = availablePermissions.availableRoles.find(role => { + return this.equalVerbs(role.verbs, permission.verbs); + }); + + if (matchingRole) { + return matchingRole.name; + } else { + return ""; + } + }; + equalVerbs = (verbs1: string[], verbs2: string[]) => { + if (!verbs1 || !verbs2) { + return false; + } + + if (verbs1.length !== verbs2.length) { + return false; + } + + return verbs1.every(verb => verbs2.includes(verb)); + }; + + deletePermission = () => { + this.props.deletePermission( + this.props.permission, + this.props.namespace, + this.props.repoName + ); + }; + + render() { + const { role, permission } = this.state; + const { availablePermissions, loading, namespace, repoName } = this.props; + const availableRoleNames = availablePermissions.availableRoles.map( + r => r.name + ); + const typeSelector = + this.props.permission._links && this.props.permission._links.update ? ( + + + + ) : ( + {role} + ); + + return ( + + {permission.name} + + + + {typeSelector} + + + + + ); + } + + handleTypeChange = (type: string) => { + this.setState({ + permission: { + ...this.state.permission, + type: type + } + }); + this.modifyPermission(type); + }; + + modifyPermission = (type: string) => { + let permission = this.state.permission; + permission.type = type; + this.props.modifyPermission( + permission, + this.props.namespace, + this.props.repoName + ); + }; + + createSelectOptions(types: string[]) { + return types.map(type => { + return { + label: type, + value: type + }; + }); + } +} + +const mapStateToProps = (state, ownProps) => { + const permission = ownProps.permission; + const loading = isModifyPermissionPending( + state, + ownProps.namespace, + ownProps.repoName, + permission + ); + const deleteLoading = isDeletePermissionPending( + state, + ownProps.namespace, + ownProps.repoName, + permission + ); + + return { loading, deleteLoading }; +}; + +const mapDispatchToProps = dispatch => { + return { + modifyPermission: ( + permission: Permission, + namespace: string, + repoName: string + ) => { + dispatch(modifyPermission(permission, namespace, repoName)); + }, + deletePermission: ( + permission: Permission, + namespace: string, + repoName: string + ) => { + dispatch(deletePermission(permission, namespace, repoName)); + } + }; +}; +export default connect( + mapStateToProps, + mapDispatchToProps +)(translate("repos")(SinglePermission)); diff --git a/scm-ui/src/repos/permissions/modules/permissions.js b/scm-ui/src/repos/permissions/modules/permissions.js index 74d0ea5bc7..02c4670815 100644 --- a/scm-ui/src/repos/permissions/modules/permissions.js +++ b/scm-ui/src/repos/permissions/modules/permissions.js @@ -534,6 +534,12 @@ export default function reducer( // selectors +export function getAvailablePermissions(state: Object) { + if (state.permissions) { + return state.permissions.available; + } +} + export function getPermissionsOfRepo( state: Object, namespace: string, From 9fecc2396071ebb99affc5ede707caca6780c026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 24 Jan 2019 09:54:04 +0100 Subject: [PATCH 24/48] Make READ the default role --- .../META-INF/scm/repository-permissions.xml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml index 0266e4e22e..4646482fe7 100644 --- a/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml +++ b/scm-webapp/src/main/resources/META-INF/scm/repository-permissions.xml @@ -12,31 +12,31 @@ - OWNER + READ - * + read + pull - WRITER + WRITE read pull push - - READER - - read - pull - - HEALTH healthCheck + + OWNER + + * + +
From 7385d6778e05e1f51d2aaa63086e63f09755ccfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 24 Jan 2019 10:29:51 +0100 Subject: [PATCH 25/48] Add permissions for new user --- .../components/CreatePermissionForm.js | 56 +++++++++++++------ .../permissions/containers/Permissions.js | 1 + .../containers/SinglePermission.js | 52 ++++++----------- .../repos/permissions/modules/permissions.js | 30 ++++++++++ 4 files changed, 87 insertions(+), 52 deletions(-) diff --git a/scm-ui/src/repos/permissions/components/CreatePermissionForm.js b/scm-ui/src/repos/permissions/components/CreatePermissionForm.js index 5fd8224be4..f5a688eb9f 100644 --- a/scm-ui/src/repos/permissions/components/CreatePermissionForm.js +++ b/scm-ui/src/repos/permissions/components/CreatePermissionForm.js @@ -4,14 +4,17 @@ import { translate } from "react-i18next"; import { Autocomplete, SubmitButton } from "@scm-manager/ui-components"; import TypeSelector from "./TypeSelector"; import type { + AvailableRepositoryPermissions, PermissionCollection, PermissionCreateEntry, SelectValue } from "@scm-manager/ui-types"; import * as validator from "./permissionValidation"; +import { findMatchingRoleName } from "../modules/permissions"; type Props = { t: string => string, + availablePermissions: AvailableRepositoryPermissions, createPermission: (permission: PermissionCreateEntry) => void, loading: boolean, currentPermissions: PermissionCollection, @@ -21,7 +24,7 @@ type Props = { type State = { name: string, - type: string, + verbs: string[], groupPermission: boolean, valid: boolean, value?: SelectValue @@ -33,7 +36,7 @@ class CreatePermissionForm extends React.Component { this.state = { name: "", - type: "READ", + verbs: props.availablePermissions.availableRoles[0].verbs, groupPermission: false, valid: true, value: undefined @@ -121,9 +124,14 @@ class CreatePermissionForm extends React.Component { }; render() { - const { t, loading } = this.props; + const { t, availablePermissions, loading } = this.props; - const { type } = this.state; + const { verbs } = this.state; + + const availableRoleNames = availablePermissions.availableRoles.map( + r => r.name + ); + const matchingRole = findMatchingRoleName(availablePermissions, verbs); return (
@@ -155,20 +163,25 @@ class CreatePermissionForm extends React.Component {
-
+
- {this.renderAutocompletionField()} + {this.renderAutocompletionField()}
- +
-
-
+
+
{ disabled={!this.state.valid || this.state.name === ""} />
-
+
); @@ -185,7 +198,7 @@ class CreatePermissionForm extends React.Component { submit = e => { this.props.createPermission({ name: this.state.name, - type: this.state.type, + verbs: this.state.verbs, groupPermission: this.state.groupPermission }); this.removeState(); @@ -195,17 +208,24 @@ class CreatePermissionForm extends React.Component { removeState = () => { this.setState({ name: "", - type: "READ", + verbs: this.props.availablePermissions.availableRoles[0].verbs, groupPermission: false, valid: true }); }; handleTypeChange = (type: string) => { + const selectedRole = this.findAvailableRole(type); this.setState({ - type: type + verbs: selectedRole.verbs }); }; + + findAvailableRole = (type: string) => { + return this.props.availablePermissions.availableRoles.find( + role => role.name === type + ); + }; } export default translate("repos")(CreatePermissionForm); diff --git a/scm-ui/src/repos/permissions/containers/Permissions.js b/scm-ui/src/repos/permissions/containers/Permissions.js index 830d71df66..3994b1e10b 100644 --- a/scm-ui/src/repos/permissions/containers/Permissions.js +++ b/scm-ui/src/repos/permissions/containers/Permissions.js @@ -128,6 +128,7 @@ class Permissions extends React.Component { const createPermissionForm = hasPermissionToCreate ? ( this.createPermission(permission)} loading={loadingCreatePermission} currentPermissions={permissions} diff --git a/scm-ui/src/repos/permissions/containers/SinglePermission.js b/scm-ui/src/repos/permissions/containers/SinglePermission.js index 1f260b4ae7..6c019afcb8 100644 --- a/scm-ui/src/repos/permissions/containers/SinglePermission.js +++ b/scm-ui/src/repos/permissions/containers/SinglePermission.js @@ -9,7 +9,8 @@ import { modifyPermission, isModifyPermissionPending, deletePermission, - isDeletePermissionPending + isDeletePermissionPending, + findMatchingRoleName } from "../modules/permissions"; import { connect } from "react-redux"; import type { History } from "history"; @@ -57,9 +58,12 @@ class SinglePermission extends React.Component { } componentDidMount() { - const { permission } = this.props; + const { availablePermissions, permission } = this.props; - const matchingRole = this.findMatchingRoleName(); + const matchingRole = findMatchingRoleName( + availablePermissions, + permission.verbs + ); if (permission) { this.setState({ @@ -74,33 +78,6 @@ class SinglePermission extends React.Component { } } - findMatchingRoleName = () => { - const { availablePermissions, permission } = this.props; - if (!permission) { - return ""; - } - const matchingRole = availablePermissions.availableRoles.find(role => { - return this.equalVerbs(role.verbs, permission.verbs); - }); - - if (matchingRole) { - return matchingRole.name; - } else { - return ""; - } - }; - equalVerbs = (verbs1: string[], verbs2: string[]) => { - if (!verbs1 || !verbs2) { - return false; - } - - if (verbs1.length !== verbs2.length) { - return false; - } - - return verbs1.every(verb => verbs2.includes(verb)); - }; - deletePermission = () => { this.props.deletePermission( this.props.permission, @@ -150,18 +127,25 @@ class SinglePermission extends React.Component { } handleTypeChange = (type: string) => { + const selectedRole = this.findAvailableRole(type); this.setState({ permission: { ...this.state.permission, - type: type + verbs: selectedRole.verbs } }); - this.modifyPermission(type); + this.modifyPermission(selectedRole.verbs); }; - modifyPermission = (type: string) => { + findAvailableRole = (type: string) => { + return this.props.availablePermissions.availableRoles.find( + role => role.name === type + ); + }; + + modifyPermission = (verbs: string[]) => { let permission = this.state.permission; - permission.type = type; + permission.verbs = verbs; this.props.modifyPermission( permission, this.props.namespace, diff --git a/scm-ui/src/repos/permissions/modules/permissions.js b/scm-ui/src/repos/permissions/modules/permissions.js index 02c4670815..cb6b013c61 100644 --- a/scm-ui/src/repos/permissions/modules/permissions.js +++ b/scm-ui/src/repos/permissions/modules/permissions.js @@ -700,3 +700,33 @@ export function getModifyPermissionsFailure( } return null; } + +export function findMatchingRoleName( + availablePermissions: AvailableRepositoryPermissions, + verbs: string[] +) { + if (!verbs) { + return ""; + } + const matchingRole = availablePermissions.availableRoles.find(role => { + return equalVerbs(role.verbs, verbs); + }); + + if (matchingRole) { + return matchingRole.name; + } else { + return ""; + } +} + +function equalVerbs(verbs1: string[], verbs2: string[]) { + if (!verbs1 || !verbs2) { + return false; + } + + if (verbs1.length !== verbs2.length) { + return false; + } + + return verbs1.every(verb => verbs2.includes(verb)); +} From f9745121170f789c12088dd0aa308d4f4de967b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 24 Jan 2019 10:30:17 +0100 Subject: [PATCH 26/48] Validate new permissions --- .../sonia/scm/api/v2/resources/RepositoryPermissionDto.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java index 0699b78e91..a896ba385d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java @@ -7,7 +7,9 @@ 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; @@ -22,6 +24,7 @@ public class RepositoryPermissionDto extends HalRepresentation { @Pattern(regexp = USER_GROUP_PATTERN) private String name; + @NotEmpty @NotNull private Collection verbs; private boolean groupPermission = false; From 905177a8577d6f251bcc31c6dc96e497adc21daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 24 Jan 2019 10:45:23 +0100 Subject: [PATCH 27/48] Set selected role after update --- scm-ui/src/repos/permissions/containers/SinglePermission.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/permissions/containers/SinglePermission.js b/scm-ui/src/repos/permissions/containers/SinglePermission.js index 6c019afcb8..cdeb536632 100644 --- a/scm-ui/src/repos/permissions/containers/SinglePermission.js +++ b/scm-ui/src/repos/permissions/containers/SinglePermission.js @@ -132,7 +132,8 @@ class SinglePermission extends React.Component { permission: { ...this.state.permission, verbs: selectedRole.verbs - } + }, + role: type }); this.modifyPermission(selectedRole.verbs); }; From 813153dfa149d6a696e561936c17979b37bc7057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 24 Jan 2019 11:53:57 +0100 Subject: [PATCH 28/48] Add advanced dialog --- scm-ui/public/locales/en/permissions.json | 7 ++ .../permissions/components/TypeSelector.js | 8 +- .../containers/AdvancedPermissionsDialog.js | 87 +++++++++++++++++++ .../containers/SinglePermission.js | 65 +++++++++++--- 4 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 scm-ui/src/repos/permissions/containers/AdvancedPermissionsDialog.js diff --git a/scm-ui/public/locales/en/permissions.json b/scm-ui/public/locales/en/permissions.json index 52059db60a..209e317cff 100644 --- a/scm-ui/public/locales/en/permissions.json +++ b/scm-ui/public/locales/en/permissions.json @@ -4,5 +4,12 @@ "label": "Set permissions" }, "set-permissions-successful": "Permissions set successfully" + }, + "advanced": { + "dialog": { + "title": "Advanced permissions", + "submit": "Submit", + "abort": "Abort" + } } } diff --git a/scm-ui/src/repos/permissions/components/TypeSelector.js b/scm-ui/src/repos/permissions/components/TypeSelector.js index 9945fa4def..4b027bdc6b 100644 --- a/scm-ui/src/repos/permissions/components/TypeSelector.js +++ b/scm-ui/src/repos/permissions/components/TypeSelector.js @@ -26,11 +26,15 @@ class TypeSelector extends React.Component { if (!availableTypes) return null; + const options = type + ? this.createSelectOptions(availableTypes) + : ["", ...this.createSelectOptions(availableTypes)]; + return ( + ); + } + + createSelectOptions(roles: string[]) { + return roles.map(role => { + return { + label: role, + value: role + }; + }); + } +} + +export default translate("repos")(RoleSelector); diff --git a/scm-ui/src/repos/permissions/components/TypeSelector.js b/scm-ui/src/repos/permissions/components/TypeSelector.js deleted file mode 100644 index 4b027bdc6b..0000000000 --- a/scm-ui/src/repos/permissions/components/TypeSelector.js +++ /dev/null @@ -1,55 +0,0 @@ -// @flow -import React from "react"; -import { translate } from "react-i18next"; -import { Select } from "@scm-manager/ui-components"; - -type Props = { - t: string => string, - availableTypes: string[], - handleTypeChange: string => void, - type: string, - label?: string, - helpText?: string, - loading?: boolean -}; - -class TypeSelector extends React.Component { - render() { - const { - availableTypes, - type, - handleTypeChange, - loading, - label, - helpText - } = this.props; - - if (!availableTypes) return null; - - const options = type - ? this.createSelectOptions(availableTypes) - : ["", ...this.createSelectOptions(availableTypes)]; - - return ( -