mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-09 15:05:44 +01:00
merge repository heads
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -68,7 +68,6 @@ import java.util.Set;
|
|||||||
@XmlRootElement(name = "repositories")
|
@XmlRootElement(name = "repositories")
|
||||||
public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject{
|
public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject{
|
||||||
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 3486560714961909711L;
|
private static final long serialVersionUID = 3486560714961909711L;
|
||||||
|
|
||||||
private String contact;
|
private String contact;
|
||||||
@@ -81,6 +80,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
private Long lastModified;
|
private Long lastModified;
|
||||||
private String namespace;
|
private String namespace;
|
||||||
private String name;
|
private String name;
|
||||||
|
@XmlElement(name = "permission")
|
||||||
private final Set<RepositoryPermission> permissions = new HashSet<>();
|
private final Set<RepositoryPermission> permissions = new HashSet<>();
|
||||||
@XmlElement(name = "public")
|
@XmlElement(name = "public")
|
||||||
private boolean publicReadable = false;
|
private boolean publicReadable = false;
|
||||||
|
|||||||
@@ -37,12 +37,19 @@ package sonia.scm.repository;
|
|||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import sonia.scm.security.PermissionObject;
|
import sonia.scm.security.PermissionObject;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.unmodifiableCollection;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -60,54 +67,19 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
|
|
||||||
private boolean groupPermission = false;
|
private boolean groupPermission = false;
|
||||||
private String name;
|
private String name;
|
||||||
private PermissionType type = PermissionType.READ;
|
@XmlElement(name = "verb")
|
||||||
|
private Collection<String> verbs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new {@link RepositoryPermission}.
|
* Constructs a new {@link RepositoryPermission}.
|
||||||
* This constructor is used by JAXB.
|
* This constructor is used by JAXB and mapstruct.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public RepositoryPermission() {}
|
public RepositoryPermission() {}
|
||||||
|
|
||||||
/**
|
public RepositoryPermission(String name, Collection<String> verbs, boolean groupPermission)
|
||||||
* Constructs a new {@link RepositoryPermission} with type = {@link PermissionType#READ}
|
|
||||||
* for the specified user.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name name of the user
|
|
||||||
*/
|
|
||||||
public RepositoryPermission(String name)
|
|
||||||
{
|
{
|
||||||
this();
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
this.verbs = unmodifiableCollection(new LinkedHashSet<>(verbs));
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.groupPermission = groupPermission;
|
this.groupPermission = groupPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +109,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
final RepositoryPermission other = (RepositoryPermission) obj;
|
final RepositoryPermission other = (RepositoryPermission) obj;
|
||||||
|
|
||||||
return Objects.equal(name, other.name)
|
return Objects.equal(name, other.name)
|
||||||
&& Objects.equal(type, other.type)
|
&& CollectionUtils.isEqualCollection(verbs, other.verbs)
|
||||||
&& Objects.equal(groupPermission, other.groupPermission);
|
&& Objects.equal(groupPermission, other.groupPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +122,9 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
return Objects.hashCode(name, type, 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -160,7 +134,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
//J-
|
//J-
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
.add("name", name)
|
.add("name", name)
|
||||||
.add("type", type)
|
.add("verbs", verbs)
|
||||||
.add("groupPermission", groupPermission)
|
.add("groupPermission", groupPermission)
|
||||||
.toString();
|
.toString();
|
||||||
//J+
|
//J+
|
||||||
@@ -181,14 +155,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 Collection<String> getVerbs()
|
||||||
{
|
{
|
||||||
return type;
|
return verbs == null? emptyList(): verbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,13 +202,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 verbs verbs of the permission
|
||||||
*/
|
*/
|
||||||
public void setType(PermissionType type)
|
public void setVerbs(Collection<String> verbs)
|
||||||
{
|
{
|
||||||
this.type = type;
|
this.verbs = verbs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ import org.apache.shiro.subject.Subject;
|
|||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.PreProcessorUtil;
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.IncomingCommand;
|
import sonia.scm.repository.spi.IncomingCommand;
|
||||||
import sonia.scm.repository.spi.IncomingCommandRequest;
|
import sonia.scm.repository.spi.IncomingCommandRequest;
|
||||||
import sonia.scm.security.RepositoryPermission;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -94,8 +93,7 @@ public final class IncomingCommandBuilder
|
|||||||
{
|
{
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
subject.checkPermission(new RepositoryPermission(remoteRepository,
|
subject.isPermitted(RepositoryPermissions.pull(remoteRepository).asShiroString());
|
||||||
PermissionType.READ));
|
|
||||||
|
|
||||||
request.setRemoteRepository(remoteRepository);
|
request.setRemoteRepository(remoteRepository);
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,11 @@ import org.apache.shiro.SecurityUtils;
|
|||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.PreProcessorUtil;
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.OutgoingCommand;
|
import sonia.scm.repository.spi.OutgoingCommand;
|
||||||
import sonia.scm.repository.spi.OutgoingCommandRequest;
|
import sonia.scm.repository.spi.OutgoingCommandRequest;
|
||||||
import sonia.scm.security.RepositoryPermission;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -84,8 +83,7 @@ public final class OutgoingCommandBuilder
|
|||||||
{
|
{
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
subject.checkPermission(new RepositoryPermission(remoteRepository,
|
subject.isPermitted(RepositoryPermissions.pull(remoteRepository).asShiroString());
|
||||||
PermissionType.READ));
|
|
||||||
|
|
||||||
request.setRemoteRepository(remoteRepository);
|
request.setRemoteRepository(remoteRepository);
|
||||||
|
|
||||||
|
|||||||
@@ -38,11 +38,10 @@ import org.apache.shiro.SecurityUtils;
|
|||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.PullCommand;
|
import sonia.scm.repository.spi.PullCommand;
|
||||||
import sonia.scm.repository.spi.PullCommandRequest;
|
import sonia.scm.repository.spi.PullCommandRequest;
|
||||||
import sonia.scm.security.RepositoryPermission;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -96,9 +95,7 @@ public final class PullCommandBuilder
|
|||||||
public PullResponse pull(String url) throws IOException {
|
public PullResponse pull(String url) throws IOException {
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
//J-
|
//J-
|
||||||
subject.checkPermission(
|
subject.isPermitted(RepositoryPermissions.push(localRepository).asShiroString());
|
||||||
new RepositoryPermission(localRepository, PermissionType.WRITE)
|
|
||||||
);
|
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
URL remoteUrl = new URL(url);
|
URL remoteUrl = new URL(url);
|
||||||
@@ -124,12 +121,8 @@ public final class PullCommandBuilder
|
|||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
//J-
|
//J-
|
||||||
subject.checkPermission(
|
subject.isPermitted(RepositoryPermissions.push(localRepository).asShiroString());
|
||||||
new RepositoryPermission(localRepository, PermissionType.WRITE)
|
subject.isPermitted(RepositoryPermissions.push(remoteRepository).asShiroString());
|
||||||
);
|
|
||||||
subject.checkPermission(
|
|
||||||
new RepositoryPermission(remoteRepository, PermissionType.READ)
|
|
||||||
);
|
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
request.reset();
|
request.reset();
|
||||||
|
|||||||
@@ -39,11 +39,10 @@ import org.apache.shiro.SecurityUtils;
|
|||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.PushCommand;
|
import sonia.scm.repository.spi.PushCommand;
|
||||||
import sonia.scm.repository.spi.PushCommandRequest;
|
import sonia.scm.repository.spi.PushCommandRequest;
|
||||||
import sonia.scm.security.RepositoryPermission;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -92,9 +91,7 @@ public final class PushCommandBuilder
|
|||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
//J-
|
//J-
|
||||||
subject.checkPermission(
|
subject.isPermitted(RepositoryPermissions.push(remoteRepository).asShiroString());
|
||||||
new RepositoryPermission(remoteRepository, PermissionType.WRITE)
|
|
||||||
);
|
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
logger.info("push changes to repository {}", remoteRepository.getId());
|
logger.info("push changes to repository {}", remoteRepository.getId());
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@ public class VndMediaType {
|
|||||||
public static final String GROUP = PREFIX + "group" + SUFFIX;
|
public static final String GROUP = PREFIX + "group" + SUFFIX;
|
||||||
public static final String AUTOCOMPLETE = PREFIX + "autocomplete" + SUFFIX;
|
public static final String AUTOCOMPLETE = PREFIX + "autocomplete" + SUFFIX;
|
||||||
public static final String REPOSITORY = PREFIX + "repository" + 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 = PREFIX + "changeset" + SUFFIX;
|
||||||
public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX;
|
public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX;
|
||||||
public static final String MODIFICATIONS = PREFIX + "modifications" + SUFFIX;
|
public static final String MODIFICATIONS = PREFIX + "modifications" + SUFFIX;
|
||||||
@@ -33,6 +33,7 @@ public class VndMediaType {
|
|||||||
public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX;
|
public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX;
|
||||||
public static final String BRANCH_COLLECTION = PREFIX + "branchCollection" + SUFFIX;
|
public static final String BRANCH_COLLECTION = PREFIX + "branchCollection" + SUFFIX;
|
||||||
public static final String CONFIG = PREFIX + "config" + 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_COLLECTION = PREFIX + "repositoryTypeCollection" + SUFFIX;
|
||||||
public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX;
|
public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX;
|
||||||
public static final String UI_PLUGIN = PREFIX + "uiPlugin" + SUFFIX;
|
public static final String UI_PLUGIN = PREFIX + "uiPlugin" + SUFFIX;
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ public abstract class PermissionFilter extends ScmProviderHttpServletDecorator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
permitted = RepositoryPermissions.read(repository).isPermitted();
|
permitted = RepositoryPermissions.pull(repository).isPermitted();
|
||||||
}
|
}
|
||||||
|
|
||||||
return permitted;
|
return permitted;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,5 +8,5 @@ unpriv = secret
|
|||||||
[roles]
|
[roles]
|
||||||
admin = *
|
admin = *
|
||||||
user = something:*
|
user = something:*
|
||||||
repo_read = "repository:read:1"
|
repo_read = "repository:read,pull:1"
|
||||||
repo_write = "repository:push:1"
|
repo_write = "repository:read,write,pull,push:1"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import sonia.scm.io.FileSystem;
|
|||||||
import sonia.scm.repository.InitialRepositoryLocationResolver;
|
import sonia.scm.repository.InitialRepositoryLocationResolver;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -24,8 +25,10 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@@ -70,9 +73,7 @@ class XmlRepositoryDAOTest {
|
|||||||
Clock clock = mock(Clock.class);
|
Clock clock = mock(Clock.class);
|
||||||
when(clock.millis()).then(ic -> atomicClock.incrementAndGet());
|
when(clock.millis()).then(ic -> atomicClock.incrementAndGet());
|
||||||
|
|
||||||
XmlRepositoryDAO dao = new XmlRepositoryDAO(context, locationResolver, fileSystem, clock);
|
return new XmlRepositoryDAO(context, locationResolver, fileSystem, clock);
|
||||||
|
|
||||||
return dao;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -329,6 +330,21 @@ class XmlRepositoryDAOTest {
|
|||||||
assertThat(content).contains("Awesome Spaceship");
|
assertThat(content).contains("Awesome Spaceship");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPersistPermissions() throws IOException {
|
||||||
|
Repository heartOfGold = createHeartOfGold();
|
||||||
|
heartOfGold.setPermissions(asList(new RepositoryPermission("trillian", asList("read", "write"), false), new RepositoryPermission("vogons", Collections.singletonList("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).containsSubsequence("trillian", "<verb>read</verb>", "<verb>write</verb>");
|
||||||
|
assertThat(content).containsSubsequence("vogons", "<verb>delete</verb>");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReadPathDatabaseAndMetadataOfRepositories() {
|
void shouldReadPathDatabaseAndMetadataOfRepositories() {
|
||||||
Repository heartOfGold = createHeartOfGold();
|
Repository heartOfGold = createHeartOfGold();
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import org.junit.runners.Parameterized;
|
|||||||
import org.junit.runners.Parameterized.Parameters;
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
import sonia.scm.it.utils.RepositoryUtil;
|
import sonia.scm.it.utils.RepositoryUtil;
|
||||||
import sonia.scm.it.utils.TestData;
|
import sonia.scm.it.utils.TestData;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.client.api.RepositoryClient;
|
import sonia.scm.repository.client.api.RepositoryClient;
|
||||||
import sonia.scm.repository.client.api.RepositoryClientException;
|
import sonia.scm.repository.client.api.RepositoryClientException;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
@@ -59,7 +58,10 @@ import static org.junit.Assert.assertNull;
|
|||||||
import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile;
|
import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile;
|
||||||
import static sonia.scm.it.utils.RestUtil.given;
|
import static sonia.scm.it.utils.RestUtil.given;
|
||||||
import static sonia.scm.it.utils.ScmTypes.availableScmTypes;
|
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.USER_SCM_ADMIN;
|
||||||
|
import static sonia.scm.it.utils.TestData.WRITE;
|
||||||
import static sonia.scm.it.utils.TestData.callRepository;
|
import static sonia.scm.it.utils.TestData.callRepository;
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
@@ -91,11 +93,11 @@ public class PermissionsITCase {
|
|||||||
public void prepareEnvironment() {
|
public void prepareEnvironment() {
|
||||||
TestData.createDefault();
|
TestData.createDefault();
|
||||||
TestData.createNotAdminUser(USER_READ, USER_PASS);
|
TestData.createNotAdminUser(USER_READ, USER_PASS);
|
||||||
TestData.createUserPermission(USER_READ, PermissionType.READ, repositoryType);
|
TestData.createUserPermission(USER_READ, READ, repositoryType);
|
||||||
TestData.createNotAdminUser(USER_WRITE, USER_PASS);
|
TestData.createNotAdminUser(USER_WRITE, USER_PASS);
|
||||||
TestData.createUserPermission(USER_WRITE, PermissionType.WRITE, repositoryType);
|
TestData.createUserPermission(USER_WRITE, WRITE, repositoryType);
|
||||||
TestData.createNotAdminUser(USER_OWNER, USER_PASS);
|
TestData.createNotAdminUser(USER_OWNER, USER_PASS);
|
||||||
TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType);
|
TestData.createUserPermission(USER_OWNER, OWNER, repositoryType);
|
||||||
TestData.createNotAdminUser(USER_OTHER, USER_PASS);
|
TestData.createNotAdminUser(USER_OTHER, USER_PASS);
|
||||||
createdPermissions = asList(USER_READ, USER_WRITE, USER_OWNER);
|
createdPermissions = asList(USER_READ, USER_WRITE, USER_OWNER);
|
||||||
}
|
}
|
||||||
@@ -109,7 +111,7 @@ public class PermissionsITCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readUserShouldNotSeeBruteForcePermissions() {
|
public void readUserShouldNotSeeBruteForcePermissions() {
|
||||||
given(VndMediaType.PERMISSION, USER_READ, USER_PASS)
|
given(VndMediaType.REPOSITORY_PERMISSION, USER_READ, USER_PASS)
|
||||||
.when()
|
.when()
|
||||||
.get(TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType))
|
.get(TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType))
|
||||||
.then()
|
.then()
|
||||||
@@ -125,7 +127,7 @@ public class PermissionsITCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void writeUserShouldNotSeeBruteForcePermissions() {
|
public void writeUserShouldNotSeeBruteForcePermissions() {
|
||||||
given(VndMediaType.PERMISSION, USER_WRITE, USER_PASS)
|
given(VndMediaType.REPOSITORY_PERMISSION, USER_WRITE, USER_PASS)
|
||||||
.when()
|
.when()
|
||||||
.get(TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType))
|
.get(TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType))
|
||||||
.then()
|
.then()
|
||||||
@@ -145,7 +147,7 @@ public class PermissionsITCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void otherUserShouldNotSeeBruteForcePermissions() {
|
public void otherUserShouldNotSeeBruteForcePermissions() {
|
||||||
given(VndMediaType.PERMISSION, USER_OTHER, USER_PASS)
|
given(VndMediaType.REPOSITORY_PERMISSION, USER_OTHER, USER_PASS)
|
||||||
.when()
|
.when()
|
||||||
.get(TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType))
|
.get(TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType))
|
||||||
.then()
|
.then()
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import io.restassured.response.ValidatableResponse;
|
|||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
import javax.json.JsonObjectBuilder;
|
import javax.json.JsonObjectBuilder;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static sonia.scm.it.utils.RestUtil.createResourceUrl;
|
import static sonia.scm.it.utils.RestUtil.createResourceUrl;
|
||||||
@@ -25,6 +26,11 @@ public class TestData {
|
|||||||
|
|
||||||
public static final String USER_SCM_ADMIN = "scmadmin";
|
public static final String USER_SCM_ADMIN = "scmadmin";
|
||||||
public static final String USER_ANONYMOUS = "anonymous";
|
public static final String USER_ANONYMOUS = "anonymous";
|
||||||
|
|
||||||
|
public static final Collection<String> READ = asList("read", "pull");
|
||||||
|
public static final Collection<String> WRITE = asList("read", "write", "pull", "push");
|
||||||
|
public static final Collection<String> OWNER = asList("*");
|
||||||
|
|
||||||
private static final List<String> PROTECTED_USERS = asList(USER_SCM_ADMIN, USER_ANONYMOUS);
|
private static final List<String> PROTECTED_USERS = asList(USER_SCM_ADMIN, USER_ANONYMOUS);
|
||||||
|
|
||||||
private static Map<String, String> DEFAULT_REPOSITORIES = new HashMap<>();
|
private static Map<String, String> DEFAULT_REPOSITORIES = new HashMap<>();
|
||||||
@@ -82,13 +88,13 @@ public class TestData {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) {
|
public static void createUserPermission(String name, Collection<String> permissionType, String repositoryType) {
|
||||||
String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, 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);
|
LOG.info("create permission with name {} and type: {} using the endpoint: {}", name, permissionType, defaultPermissionUrl);
|
||||||
given(VndMediaType.PERMISSION)
|
given(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
.when()
|
.when()
|
||||||
.content("{\n" +
|
.content("{\n" +
|
||||||
"\t\"type\": \"" + permissionType.name() + "\",\n" +
|
"\t\"verbs\": " + permissionType.stream().collect(Collectors.joining("\",\"", "[\"", "\"]")) + ",\n" +
|
||||||
"\t\"name\": \"" + name + "\",\n" +
|
"\t\"name\": \"" + name + "\",\n" +
|
||||||
"\t\"groupPermission\": false\n" +
|
"\t\"groupPermission\": false\n" +
|
||||||
"\t\n" +
|
"\t\n" +
|
||||||
@@ -106,7 +112,7 @@ public class TestData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ValidatableResponse callUserPermissions(String username, String password, String repositoryType, int expectedStatusCode) {
|
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()
|
.when()
|
||||||
.get(TestData.getDefaultPermissionUrl(username, password, repositoryType))
|
.get(TestData.getDefaultPermissionUrl(username, password, repositoryType))
|
||||||
.then()
|
.then()
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import injectSheet from "react-jss";
|
import injectSheet from "react-jss";
|
||||||
import SubmitButton, { type ButtonProps } from "./SubmitButton";
|
import { type ButtonProps } from "./Button";
|
||||||
import classNames from "classnames";
|
import SubmitButton from "./SubmitButton";
|
||||||
|
import classNames from "classnames";
|
||||||
const styles = {
|
|
||||||
spacing: {
|
const styles = {
|
||||||
marginTop: "2em",
|
spacing: {
|
||||||
border: "2px solid #e9f7fd",
|
marginTop: "2em",
|
||||||
padding: "1em 1em"
|
border: "2px solid #e9f7fd",
|
||||||
}
|
padding: "1em 1em"
|
||||||
|
}
|
||||||
};
|
|
||||||
|
};
|
||||||
class CreateButton extends React.Component<ButtonProps> {
|
|
||||||
render() {
|
class CreateButton extends React.Component<ButtonProps> {
|
||||||
const { classes } = this.props;
|
render() {
|
||||||
return (
|
const { classes } = this.props;
|
||||||
<div className={classNames("has-text-centered", classes.spacing)}>
|
return (
|
||||||
<SubmitButton {...this.props} />
|
<div className={classNames("has-text-centered", classes.spacing)}>
|
||||||
</div>
|
<SubmitButton {...this.props} />
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
export default injectSheet(styles)(CreateButton);
|
|
||||||
|
export default injectSheet(styles)(CreateButton);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class Select extends React.Component<Props> {
|
|||||||
>
|
>
|
||||||
{options.map(opt => {
|
{options.map(opt => {
|
||||||
return (
|
return (
|
||||||
<option value={opt.value} key={opt.value}>
|
<option value={opt.value} key={"KEY_" + opt.value}>
|
||||||
{opt.label}
|
{opt.label}
|
||||||
</option>
|
</option>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
export type RepositoryRole = {
|
||||||
|
name: string,
|
||||||
|
verbs: string[]
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AvailableRepositoryPermissions = {
|
||||||
|
availableVerbs: string[],
|
||||||
|
availableRoles: RepositoryRole[]
|
||||||
|
};
|
||||||
@@ -7,7 +7,7 @@ export type Permission = PermissionCreateEntry & {
|
|||||||
|
|
||||||
export type PermissionCreateEntry = {
|
export type PermissionCreateEntry = {
|
||||||
name: string,
|
name: string,
|
||||||
type: string,
|
verbs: string[],
|
||||||
groupPermission: boolean
|
groupPermission: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,3 +24,5 @@ export type { Permission, PermissionCreateEntry, PermissionCollection } from "./
|
|||||||
export type { SubRepository, File } from "./Sources";
|
export type { SubRepository, File } from "./Sources";
|
||||||
|
|
||||||
export type { SelectValue, AutocompleteObject } from "./Autocomplete";
|
export type { SelectValue, AutocompleteObject } from "./Autocomplete";
|
||||||
|
|
||||||
|
export type { AvailableRepositoryPermissions, RepositoryRole } from "./AvailableRepositoryPermissions";
|
||||||
|
|||||||
@@ -87,44 +87,56 @@
|
|||||||
"label": "Branches"
|
"label": "Branches"
|
||||||
},
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"group": "Group",
|
"group": "Group",
|
||||||
"error-title": "Error",
|
"error-title": "Error",
|
||||||
"error-subtitle": "Unknown permissions error",
|
"error-subtitle": "Unknown permissions error",
|
||||||
"name": "User or Group",
|
"name": "User or group",
|
||||||
"type": "Type",
|
"role": "Role",
|
||||||
"group-permission": "Group Permission",
|
"permissions": "Permissions",
|
||||||
"user-permission": "User Permission",
|
"group-permission": "Group Permission",
|
||||||
"edit-permission": {
|
"user-permission": "User Permission",
|
||||||
"delete-button": "Delete",
|
"edit-permission": {
|
||||||
"save-button": "Save Changes"
|
"delete-button": "Delete",
|
||||||
},
|
"save-button": "Save Changes"
|
||||||
"delete-permission-button": {
|
},
|
||||||
"label": "Delete",
|
"advanced-button": {
|
||||||
"confirm-alert": {
|
"label": "Advanced"
|
||||||
"title": "Delete permission",
|
},
|
||||||
"message": "Do you really want to delete the permission?",
|
"delete-permission-button": {
|
||||||
"submit": "Yes",
|
"label": "Delete",
|
||||||
"cancel": "No"
|
"confirm-alert": {
|
||||||
}
|
"title": "Delete permission",
|
||||||
},
|
"message": "Do you really want to delete the permission?",
|
||||||
"add-permission": {
|
"submit": "Yes",
|
||||||
"add-permission-heading": "Add new Permission",
|
"cancel": "No"
|
||||||
"submit-button": "Submit",
|
|
||||||
"name-input-invalid": "Permission is not allowed to be empty! If it is not empty, your input name is invalid or it already exists!"
|
|
||||||
},
|
|
||||||
"help": {
|
|
||||||
"groupPermissionHelpText": "States if a permission is a group permission.",
|
|
||||||
"nameHelpText": "Manage permissions for a specific user or group",
|
|
||||||
"typeHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions"
|
|
||||||
},
|
|
||||||
"autocomplete": {
|
|
||||||
"no-group-options": "No group suggestion available",
|
|
||||||
"group-placeholder": "Enter group",
|
|
||||||
"no-user-options": "No user suggestion available",
|
|
||||||
"user-placeholder": "Enter user",
|
|
||||||
"loading": "Loading..."
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"add-permission": {
|
||||||
|
"add-permission-heading": "Add new Permission",
|
||||||
|
"submit-button": "Submit",
|
||||||
|
"name-input-invalid": "Permission is not allowed to be empty! If it is not empty, your input name is invalid or it already exists!"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"groupPermissionHelpText": "States if a permission is a group permission. If this is not checked, it is a user permission.",
|
||||||
|
"nameHelpText": "Manage permissions for a specific user or group",
|
||||||
|
"roleHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions. If nothing is selected here, use the 'Advanced' Button to see detailed permissions.",
|
||||||
|
"permissionsHelpText": "Use this to specify your own set of permissions regardless of predefined roles"
|
||||||
|
},
|
||||||
|
"autocomplete": {
|
||||||
|
"no-group-options": "No group suggestion available",
|
||||||
|
"group-placeholder": "Enter group",
|
||||||
|
"no-user-options": "No user suggestion available",
|
||||||
|
"user-placeholder": "Enter user",
|
||||||
|
"loading": "Loading..."
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"dialog": {
|
||||||
|
"title": "Advanced permissions",
|
||||||
|
"submit": "Submit",
|
||||||
|
"abort": "Abort"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"nameHelpText": "The name of the repository. This name will be part of the repository url.",
|
"nameHelpText": "The name of the repository. This name will be part of the repository url.",
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import { Checkbox } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
t: string => string,
|
||||||
|
disabled: boolean,
|
||||||
|
name: string,
|
||||||
|
checked: boolean,
|
||||||
|
onChange?: (value: boolean, name?: string) => void
|
||||||
|
};
|
||||||
|
|
||||||
|
class PermissionCheckbox extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { t } = this.props;
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
key={this.props.name}
|
||||||
|
name={this.props.name}
|
||||||
|
helpText={t("verbs.repository." + this.props.name + ".description")}
|
||||||
|
label={t("verbs.repository." + this.props.name + ".displayName")}
|
||||||
|
checked={this.props.checked}
|
||||||
|
onChange={this.props.onChange}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("plugins")(PermissionCheckbox);
|
||||||
55
scm-ui/src/repos/permissions/components/RoleSelector.js
Normal file
55
scm-ui/src/repos/permissions/components/RoleSelector.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import { Select } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
t: string => string,
|
||||||
|
availableRoles: string[],
|
||||||
|
handleRoleChange: string => void,
|
||||||
|
role: string,
|
||||||
|
label?: string,
|
||||||
|
helpText?: string,
|
||||||
|
loading?: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
class RoleSelector extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
availableRoles,
|
||||||
|
role,
|
||||||
|
handleRoleChange,
|
||||||
|
loading,
|
||||||
|
label,
|
||||||
|
helpText
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!availableRoles) return null;
|
||||||
|
|
||||||
|
const options = role
|
||||||
|
? this.createSelectOptions(availableRoles)
|
||||||
|
: ["", ...this.createSelectOptions(availableRoles)];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
onChange={handleRoleChange}
|
||||||
|
value={role ? role : ""}
|
||||||
|
options={options}
|
||||||
|
loading={loading}
|
||||||
|
label={label}
|
||||||
|
helpText={helpText}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createSelectOptions(roles: string[]) {
|
||||||
|
return roles.map(role => {
|
||||||
|
return {
|
||||||
|
label: role,
|
||||||
|
value: role
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("repos")(RoleSelector);
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// @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<Props> {
|
|
||||||
render() {
|
|
||||||
const { type, handleTypeChange, loading, label, helpText } = this.props;
|
|
||||||
const types = ["READ", "OWNER", "WRITE"];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
onChange={handleTypeChange}
|
|
||||||
value={type ? type : "READ"}
|
|
||||||
options={this.createSelectOptions(types)}
|
|
||||||
loading={loading}
|
|
||||||
label={label}
|
|
||||||
helpText={helpText}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
createSelectOptions(types: string[]) {
|
|
||||||
return types.map(type => {
|
|
||||||
return {
|
|
||||||
label: type,
|
|
||||||
value: type
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translate("repos")(TypeSelector);
|
|
||||||
@@ -18,7 +18,8 @@ describe("permission validation", () => {
|
|||||||
name: "PermissionName",
|
name: "PermissionName",
|
||||||
groupPermission: true,
|
groupPermission: true,
|
||||||
type: "READ",
|
type: "READ",
|
||||||
_links: {}
|
_links: {},
|
||||||
|
verbs: []
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const name = "PermissionName";
|
const name = "PermissionName";
|
||||||
@@ -35,7 +36,8 @@ describe("permission validation", () => {
|
|||||||
name: "PermissionName",
|
name: "PermissionName",
|
||||||
groupPermission: false,
|
groupPermission: false,
|
||||||
type: "READ",
|
type: "READ",
|
||||||
_links: {}
|
_links: {},
|
||||||
|
verbs: []
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const name = "PermissionName";
|
const name = "PermissionName";
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Button, SubmitButton } from "@scm-manager/ui-components";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import PermissionCheckbox from "../components/PermissionCheckbox";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
readOnly: boolean,
|
||||||
|
availableVerbs: string[],
|
||||||
|
selectedVerbs: string[],
|
||||||
|
onSubmit: (string[]) => void,
|
||||||
|
onClose: () => void,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
t: string => string
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
verbs: any
|
||||||
|
};
|
||||||
|
|
||||||
|
class AdvancedPermissionsDialog extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const verbs = {};
|
||||||
|
props.availableVerbs.forEach(
|
||||||
|
verb => (verbs[verb] = props.selectedVerbs.includes(verb))
|
||||||
|
);
|
||||||
|
|
||||||
|
this.state = { verbs };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { t, onClose, readOnly } = this.props;
|
||||||
|
const { verbs } = this.state;
|
||||||
|
|
||||||
|
const verbSelectBoxes = Object.entries(verbs).map(e => (
|
||||||
|
<PermissionCheckbox
|
||||||
|
disabled={readOnly}
|
||||||
|
name={e[0]}
|
||||||
|
checked={e[1]}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
const submitButton = !readOnly ? (
|
||||||
|
<SubmitButton label={t("permission.advanced.dialog.submit")} />
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={"modal is-active"}>
|
||||||
|
<div className="modal-background" />
|
||||||
|
<div className="modal-card">
|
||||||
|
<header className="modal-card-head">
|
||||||
|
<p className="modal-card-title">
|
||||||
|
{t("permission.advanced.dialog.title")}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className="delete"
|
||||||
|
aria-label="close"
|
||||||
|
onClick={() => onClose()}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<section className="modal-card-body">
|
||||||
|
<div className="content">{verbSelectBoxes}</div>
|
||||||
|
<form onSubmit={this.onSubmit}>
|
||||||
|
{submitButton}
|
||||||
|
<Button
|
||||||
|
label={t("permission.advanced.dialog.abort")}
|
||||||
|
action={onClose}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (value: boolean, name: string) => {
|
||||||
|
const { verbs } = this.state;
|
||||||
|
const newVerbs = { ...verbs, [name]: value };
|
||||||
|
this.setState({ verbs: newVerbs });
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit = () => {
|
||||||
|
this.props.onSubmit(
|
||||||
|
Object.entries(this.state.verbs)
|
||||||
|
.filter(e => e[1])
|
||||||
|
.map(e => e[0])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("repos")(AdvancedPermissionsDialog);
|
||||||
@@ -1,17 +1,26 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { Autocomplete, Radio, SubmitButton } from "@scm-manager/ui-components";
|
import {
|
||||||
import TypeSelector from "./TypeSelector";
|
Autocomplete,
|
||||||
|
SubmitButton,
|
||||||
|
Button,
|
||||||
|
LabelWithHelpIcon
|
||||||
|
} from "@scm-manager/ui-components";
|
||||||
|
import RoleSelector from "../components/RoleSelector";
|
||||||
import type {
|
import type {
|
||||||
|
AvailableRepositoryPermissions,
|
||||||
PermissionCollection,
|
PermissionCollection,
|
||||||
PermissionCreateEntry,
|
PermissionCreateEntry,
|
||||||
SelectValue
|
SelectValue
|
||||||
} from "@scm-manager/ui-types";
|
} from "@scm-manager/ui-types";
|
||||||
import * as validator from "./permissionValidation";
|
import * as validator from "../components/permissionValidation";
|
||||||
|
import { findMatchingRoleName } from "../modules/permissions";
|
||||||
|
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
|
availablePermissions: AvailableRepositoryPermissions,
|
||||||
createPermission: (permission: PermissionCreateEntry) => void,
|
createPermission: (permission: PermissionCreateEntry) => void,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
currentPermissions: PermissionCollection,
|
currentPermissions: PermissionCollection,
|
||||||
@@ -21,10 +30,11 @@ type Props = {
|
|||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
name: string,
|
name: string,
|
||||||
type: string,
|
verbs: string[],
|
||||||
groupPermission: boolean,
|
groupPermission: boolean,
|
||||||
valid: boolean,
|
valid: boolean,
|
||||||
value?: SelectValue
|
value?: SelectValue,
|
||||||
|
showAdvancedDialog: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
class CreatePermissionForm extends React.Component<Props, State> {
|
class CreatePermissionForm extends React.Component<Props, State> {
|
||||||
@@ -33,10 +43,11 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
name: "",
|
name: "",
|
||||||
type: "READ",
|
verbs: props.availablePermissions.availableRoles[0].verbs,
|
||||||
groupPermission: false,
|
groupPermission: false,
|
||||||
valid: true,
|
valid: true,
|
||||||
value: undefined
|
value: undefined,
|
||||||
|
showAdvancedDialog: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,9 +132,23 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t, loading } = this.props;
|
const { t, availablePermissions, loading } = this.props;
|
||||||
|
|
||||||
const { type } = this.state;
|
const { verbs, showAdvancedDialog } = this.state;
|
||||||
|
|
||||||
|
const availableRoleNames = availablePermissions.availableRoles.map(
|
||||||
|
r => r.name
|
||||||
|
);
|
||||||
|
const matchingRole = findMatchingRoleName(availablePermissions, verbs);
|
||||||
|
|
||||||
|
const advancedDialog = showAdvancedDialog ? (
|
||||||
|
<AdvancedPermissionsDialog
|
||||||
|
availableVerbs={availablePermissions.availableVerbs}
|
||||||
|
selectedVerbs={verbs}
|
||||||
|
onClose={this.closeAdvancedPermissionsDialog}
|
||||||
|
onSubmit={this.submitAdvancedPermissionsDialog}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -131,32 +156,57 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
|||||||
<h2 className="subtitle">
|
<h2 className="subtitle">
|
||||||
{t("permission.add-permission.add-permission-heading")}
|
{t("permission.add-permission.add-permission-heading")}
|
||||||
</h2>
|
</h2>
|
||||||
|
{advancedDialog}
|
||||||
<form onSubmit={this.submit}>
|
<form onSubmit={this.submit}>
|
||||||
<Radio
|
<div className="control">
|
||||||
name="permission_scope"
|
<label className="radio">
|
||||||
value="USER_PERMISSION"
|
<input
|
||||||
checked={!this.state.groupPermission}
|
type="radio"
|
||||||
label={t("permission.user-permission")}
|
name="permission_scope"
|
||||||
onChange={this.permissionScopeChanged}
|
checked={!this.state.groupPermission}
|
||||||
/>
|
value="USER_PERMISSION"
|
||||||
<Radio
|
onChange={this.permissionScopeChanged}
|
||||||
name="permission_scope"
|
/>
|
||||||
value="GROUP_PERMISSION"
|
{t("permission.user-permission")}
|
||||||
checked={this.state.groupPermission}
|
</label>
|
||||||
label={t("permission.group-permission")}
|
<label className="radio">
|
||||||
onChange={this.permissionScopeChanged}
|
<input
|
||||||
/>
|
type="radio"
|
||||||
|
name="permission_scope"
|
||||||
|
value="GROUP_PERMISSION"
|
||||||
|
checked={this.state.groupPermission}
|
||||||
|
onChange={this.permissionScopeChanged}
|
||||||
|
/>
|
||||||
|
{t("permission.group-permission")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column is-three-quarters">
|
<div className="column is-two-thirds">
|
||||||
{this.renderAutocompletionField()}
|
{this.renderAutocompletionField()}
|
||||||
</div>
|
</div>
|
||||||
<div className="column is-one-quarter">
|
<div className="column is-one-third">
|
||||||
<TypeSelector
|
<div className="columns">
|
||||||
label={t("permission.type")}
|
<div className="column is-half">
|
||||||
helpText={t("permission.help.typeHelpText")}
|
<RoleSelector
|
||||||
handleTypeChange={this.handleTypeChange}
|
availableRoles={availableRoleNames}
|
||||||
type={type ? type : "READ"}
|
label={t("permission.role")}
|
||||||
/>
|
helpText={t("permission.help.roleHelpText")}
|
||||||
|
handleRoleChange={this.handleRoleChange}
|
||||||
|
role={matchingRole}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="column is-half">
|
||||||
|
<LabelWithHelpIcon
|
||||||
|
label={t("permission.permissions")}
|
||||||
|
helpText={t("permission.help.permissionsHelpText")}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label={t("permission.advanced-button.label")}
|
||||||
|
action={this.handleDetailedPermissionsPressed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
@@ -173,10 +223,25 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDetailedPermissionsPressed = () => {
|
||||||
|
this.setState({ showAdvancedDialog: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
closeAdvancedPermissionsDialog = () => {
|
||||||
|
this.setState({ showAdvancedDialog: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
submitAdvancedPermissionsDialog = (newVerbs: string[]) => {
|
||||||
|
this.setState({
|
||||||
|
showAdvancedDialog: false,
|
||||||
|
verbs: newVerbs
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
submit = e => {
|
submit = e => {
|
||||||
this.props.createPermission({
|
this.props.createPermission({
|
||||||
name: this.state.name,
|
name: this.state.name,
|
||||||
type: this.state.type,
|
verbs: this.state.verbs,
|
||||||
groupPermission: this.state.groupPermission
|
groupPermission: this.state.groupPermission
|
||||||
});
|
});
|
||||||
this.removeState();
|
this.removeState();
|
||||||
@@ -186,17 +251,24 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
|||||||
removeState = () => {
|
removeState = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
name: "",
|
name: "",
|
||||||
type: "READ",
|
verbs: this.props.availablePermissions.availableRoles[0].verbs,
|
||||||
groupPermission: false,
|
groupPermission: false,
|
||||||
valid: true
|
valid: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleTypeChange = (type: string) => {
|
handleRoleChange = (role: string) => {
|
||||||
|
const selectedRole = this.findAvailableRole(role);
|
||||||
this.setState({
|
this.setState({
|
||||||
type: type
|
verbs: selectedRole.verbs
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
findAvailableRole = (roleName: string) => {
|
||||||
|
return this.props.availablePermissions.availableRoles.find(
|
||||||
|
role => role.name === roleName
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("repos")(CreatePermissionForm);
|
export default translate("repos")(CreatePermissionForm);
|
||||||
@@ -1,225 +1,268 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
fetchPermissions,
|
fetchAvailablePermissionsIfNeeded,
|
||||||
getFetchPermissionsFailure,
|
fetchPermissions,
|
||||||
isFetchPermissionsPending,
|
getFetchAvailablePermissionsFailure,
|
||||||
getPermissionsOfRepo,
|
getAvailablePermissions,
|
||||||
hasCreatePermission,
|
getFetchPermissionsFailure,
|
||||||
createPermission,
|
isFetchAvailablePermissionsPending,
|
||||||
isCreatePermissionPending,
|
isFetchPermissionsPending,
|
||||||
getCreatePermissionFailure,
|
getPermissionsOfRepo,
|
||||||
createPermissionReset,
|
hasCreatePermission,
|
||||||
getDeletePermissionsFailure,
|
createPermission,
|
||||||
getModifyPermissionsFailure,
|
isCreatePermissionPending,
|
||||||
modifyPermissionReset,
|
getCreatePermissionFailure,
|
||||||
deletePermissionReset
|
createPermissionReset,
|
||||||
} from "../modules/permissions";
|
getDeletePermissionsFailure,
|
||||||
import { Loading, ErrorPage } from "@scm-manager/ui-components";
|
getModifyPermissionsFailure,
|
||||||
import type {
|
modifyPermissionReset,
|
||||||
Permission,
|
deletePermissionReset
|
||||||
PermissionCollection,
|
} from "../modules/permissions";
|
||||||
PermissionCreateEntry
|
import {
|
||||||
} from "@scm-manager/ui-types";
|
Loading,
|
||||||
import SinglePermission from "./SinglePermission";
|
ErrorPage,
|
||||||
import CreatePermissionForm from "../components/CreatePermissionForm";
|
LabelWithHelpIcon
|
||||||
import type { History } from "history";
|
} from "@scm-manager/ui-components";
|
||||||
import { getPermissionsLink } from "../../modules/repos";
|
import type {
|
||||||
import {
|
AvailableRepositoryPermissions,
|
||||||
getGroupAutoCompleteLink,
|
Permission,
|
||||||
getUserAutoCompleteLink
|
PermissionCollection,
|
||||||
} from "../../../modules/indexResource";
|
PermissionCreateEntry
|
||||||
|
} from "@scm-manager/ui-types";
|
||||||
type Props = {
|
import SinglePermission from "./SinglePermission";
|
||||||
namespace: string,
|
import CreatePermissionForm from "./CreatePermissionForm";
|
||||||
repoName: string,
|
import type { History } from "history";
|
||||||
loading: boolean,
|
import { getPermissionsLink } from "../../modules/repos";
|
||||||
error: Error,
|
import {
|
||||||
permissions: PermissionCollection,
|
getGroupAutoCompleteLink,
|
||||||
hasPermissionToCreate: boolean,
|
getUserAutoCompleteLink
|
||||||
loadingCreatePermission: boolean,
|
} from "../../../modules/indexResource";
|
||||||
permissionsLink: string,
|
|
||||||
groupAutoCompleteLink: string,
|
type Props = {
|
||||||
userAutoCompleteLink: string,
|
availablePermissions: AvailableRepositoryPermissions,
|
||||||
|
namespace: string,
|
||||||
//dispatch functions
|
repoName: string,
|
||||||
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
loading: boolean,
|
||||||
createPermission: (
|
error: Error,
|
||||||
link: string,
|
permissions: PermissionCollection,
|
||||||
permission: PermissionCreateEntry,
|
hasPermissionToCreate: boolean,
|
||||||
namespace: string,
|
loadingCreatePermission: boolean,
|
||||||
repoName: string,
|
permissionsLink: string,
|
||||||
callback?: () => void
|
groupAutoCompleteLink: string,
|
||||||
) => void,
|
userAutoCompleteLink: string,
|
||||||
createPermissionReset: (string, string) => void,
|
|
||||||
modifyPermissionReset: (string, string) => void,
|
//dispatch functions
|
||||||
deletePermissionReset: (string, string) => void,
|
fetchAvailablePermissionsIfNeeded: () => void,
|
||||||
// context props
|
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
||||||
t: string => string,
|
createPermission: (
|
||||||
match: any,
|
link: string,
|
||||||
history: History
|
permission: PermissionCreateEntry,
|
||||||
};
|
namespace: string,
|
||||||
|
repoName: string,
|
||||||
class Permissions extends React.Component<Props> {
|
callback?: () => void
|
||||||
componentDidMount() {
|
) => void,
|
||||||
const {
|
createPermissionReset: (string, string) => void,
|
||||||
fetchPermissions,
|
modifyPermissionReset: (string, string) => void,
|
||||||
namespace,
|
deletePermissionReset: (string, string) => void,
|
||||||
repoName,
|
// context props
|
||||||
modifyPermissionReset,
|
t: string => string,
|
||||||
createPermissionReset,
|
match: any,
|
||||||
deletePermissionReset,
|
history: History
|
||||||
permissionsLink
|
};
|
||||||
} = this.props;
|
|
||||||
|
class Permissions extends React.Component<Props> {
|
||||||
createPermissionReset(namespace, repoName);
|
componentDidMount() {
|
||||||
modifyPermissionReset(namespace, repoName);
|
const {
|
||||||
deletePermissionReset(namespace, repoName);
|
fetchAvailablePermissionsIfNeeded,
|
||||||
fetchPermissions(permissionsLink, namespace, repoName);
|
fetchPermissions,
|
||||||
}
|
namespace,
|
||||||
|
repoName,
|
||||||
createPermission = (permission: Permission) => {
|
modifyPermissionReset,
|
||||||
this.props.createPermission(
|
createPermissionReset,
|
||||||
this.props.permissionsLink,
|
deletePermissionReset,
|
||||||
permission,
|
permissionsLink
|
||||||
this.props.namespace,
|
} = this.props;
|
||||||
this.props.repoName
|
|
||||||
);
|
createPermissionReset(namespace, repoName);
|
||||||
};
|
modifyPermissionReset(namespace, repoName);
|
||||||
|
deletePermissionReset(namespace, repoName);
|
||||||
render() {
|
fetchAvailablePermissionsIfNeeded();
|
||||||
const {
|
fetchPermissions(permissionsLink, namespace, repoName);
|
||||||
loading,
|
}
|
||||||
error,
|
|
||||||
permissions,
|
createPermission = (permission: Permission) => {
|
||||||
t,
|
this.props.createPermission(
|
||||||
namespace,
|
this.props.permissionsLink,
|
||||||
repoName,
|
permission,
|
||||||
loadingCreatePermission,
|
this.props.namespace,
|
||||||
hasPermissionToCreate,
|
this.props.repoName
|
||||||
userAutoCompleteLink,
|
);
|
||||||
groupAutoCompleteLink
|
};
|
||||||
} = this.props;
|
|
||||||
if (error) {
|
render() {
|
||||||
return (
|
const {
|
||||||
<ErrorPage
|
availablePermissions,
|
||||||
title={t("permission.error-title")}
|
loading,
|
||||||
subtitle={t("permission.error-subtitle")}
|
error,
|
||||||
error={error}
|
permissions,
|
||||||
/>
|
t,
|
||||||
);
|
namespace,
|
||||||
}
|
repoName,
|
||||||
|
loadingCreatePermission,
|
||||||
if (loading || !permissions) {
|
hasPermissionToCreate,
|
||||||
return <Loading />;
|
userAutoCompleteLink,
|
||||||
}
|
groupAutoCompleteLink
|
||||||
|
} = this.props;
|
||||||
const createPermissionForm = hasPermissionToCreate ? (
|
if (error) {
|
||||||
<CreatePermissionForm
|
return (
|
||||||
createPermission={permission => this.createPermission(permission)}
|
<ErrorPage
|
||||||
loading={loadingCreatePermission}
|
title={t("permission.error-title")}
|
||||||
currentPermissions={permissions}
|
subtitle={t("permission.error-subtitle")}
|
||||||
userAutoCompleteLink={userAutoCompleteLink}
|
error={error}
|
||||||
groupAutoCompleteLink={groupAutoCompleteLink}
|
/>
|
||||||
/>
|
);
|
||||||
) : null;
|
}
|
||||||
|
|
||||||
return (
|
if (loading || !permissions || !availablePermissions) {
|
||||||
<div>
|
return <Loading />;
|
||||||
<table className="has-background-light table is-hoverable is-fullwidth">
|
}
|
||||||
<thead>
|
|
||||||
<tr>
|
const createPermissionForm = hasPermissionToCreate ? (
|
||||||
<th>{t("permission.name")}</th>
|
<CreatePermissionForm
|
||||||
<th className="is-hidden-mobile">
|
availablePermissions={availablePermissions}
|
||||||
{t("permission.group-permission")}
|
createPermission={permission => this.createPermission(permission)}
|
||||||
</th>
|
loading={loadingCreatePermission}
|
||||||
<th>{t("permission.type")}</th>
|
currentPermissions={permissions}
|
||||||
<th />
|
userAutoCompleteLink={userAutoCompleteLink}
|
||||||
</tr>
|
groupAutoCompleteLink={groupAutoCompleteLink}
|
||||||
</thead>
|
/>
|
||||||
<tbody>
|
) : null;
|
||||||
{permissions.map(permission => {
|
|
||||||
return (
|
return (
|
||||||
<SinglePermission
|
<div>
|
||||||
key={permission.name + permission.groupPermission.toString()}
|
<table className="has-background-light table is-hoverable is-fullwidth">
|
||||||
namespace={namespace}
|
<thead>
|
||||||
repoName={repoName}
|
<tr>
|
||||||
permission={permission}
|
<th>
|
||||||
/>
|
<LabelWithHelpIcon
|
||||||
);
|
label={t("permission.name")}
|
||||||
})}
|
helpText={t("permission.help.nameHelpText")}
|
||||||
</tbody>
|
/>
|
||||||
</table>
|
</th>
|
||||||
{createPermissionForm}
|
<th className="is-hidden-mobile">
|
||||||
</div>
|
<LabelWithHelpIcon
|
||||||
);
|
label={t("permission.group-permission")}
|
||||||
}
|
helpText={t("permission.help.groupPermissionHelpText")}
|
||||||
}
|
/>
|
||||||
|
</th>
|
||||||
const mapStateToProps = (state, ownProps) => {
|
<th>
|
||||||
const namespace = ownProps.namespace;
|
<LabelWithHelpIcon
|
||||||
const repoName = ownProps.repoName;
|
label={t("permission.role")}
|
||||||
const error =
|
helpText={t("permission.help.roleHelpText")}
|
||||||
getFetchPermissionsFailure(state, namespace, repoName) ||
|
/>
|
||||||
getCreatePermissionFailure(state, namespace, repoName) ||
|
</th>
|
||||||
getDeletePermissionsFailure(state, namespace, repoName) ||
|
<th>
|
||||||
getModifyPermissionsFailure(state, namespace, repoName);
|
<LabelWithHelpIcon
|
||||||
const loading = isFetchPermissionsPending(state, namespace, repoName);
|
label={t("permission.permissions")}
|
||||||
const permissions = getPermissionsOfRepo(state, namespace, repoName);
|
helpText={t("permission.help.permissionsHelpText")}
|
||||||
const loadingCreatePermission = isCreatePermissionPending(
|
/>
|
||||||
state,
|
</th>
|
||||||
namespace,
|
<th />
|
||||||
repoName
|
</tr>
|
||||||
);
|
</thead>
|
||||||
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
<tbody>
|
||||||
const permissionsLink = getPermissionsLink(state, namespace, repoName);
|
{permissions.map(permission => {
|
||||||
const groupAutoCompleteLink = getGroupAutoCompleteLink(state);
|
return (
|
||||||
const userAutoCompleteLink = getUserAutoCompleteLink(state);
|
<SinglePermission
|
||||||
return {
|
availablePermissions={availablePermissions}
|
||||||
namespace,
|
key={permission.name + permission.groupPermission.toString()}
|
||||||
repoName,
|
namespace={namespace}
|
||||||
error,
|
repoName={repoName}
|
||||||
loading,
|
permission={permission}
|
||||||
permissions,
|
/>
|
||||||
hasPermissionToCreate,
|
);
|
||||||
loadingCreatePermission,
|
})}
|
||||||
permissionsLink,
|
</tbody>
|
||||||
groupAutoCompleteLink,
|
</table>
|
||||||
userAutoCompleteLink
|
{createPermissionForm}
|
||||||
};
|
</div>
|
||||||
};
|
);
|
||||||
|
}
|
||||||
const mapDispatchToProps = dispatch => {
|
}
|
||||||
return {
|
|
||||||
fetchPermissions: (link: string, namespace: string, repoName: string) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
dispatch(fetchPermissions(link, namespace, repoName));
|
const namespace = ownProps.namespace;
|
||||||
},
|
const repoName = ownProps.repoName;
|
||||||
createPermission: (
|
const error =
|
||||||
link: string,
|
getFetchPermissionsFailure(state, namespace, repoName) ||
|
||||||
permission: PermissionCreateEntry,
|
getCreatePermissionFailure(state, namespace, repoName) ||
|
||||||
namespace: string,
|
getDeletePermissionsFailure(state, namespace, repoName) ||
|
||||||
repoName: string,
|
getModifyPermissionsFailure(state, namespace, repoName) ||
|
||||||
callback?: () => void
|
getFetchAvailablePermissionsFailure(state);
|
||||||
) => {
|
const loading =
|
||||||
dispatch(
|
isFetchPermissionsPending(state, namespace, repoName) ||
|
||||||
createPermission(link, permission, namespace, repoName, callback)
|
isFetchAvailablePermissionsPending(state);
|
||||||
);
|
const permissions = getPermissionsOfRepo(state, namespace, repoName);
|
||||||
},
|
const loadingCreatePermission = isCreatePermissionPending(
|
||||||
createPermissionReset: (namespace: string, repoName: string) => {
|
state,
|
||||||
dispatch(createPermissionReset(namespace, repoName));
|
namespace,
|
||||||
},
|
repoName
|
||||||
modifyPermissionReset: (namespace: string, repoName: string) => {
|
);
|
||||||
dispatch(modifyPermissionReset(namespace, repoName));
|
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
||||||
},
|
const permissionsLink = getPermissionsLink(state, namespace, repoName);
|
||||||
deletePermissionReset: (namespace: string, repoName: string) => {
|
const groupAutoCompleteLink = getGroupAutoCompleteLink(state);
|
||||||
dispatch(deletePermissionReset(namespace, repoName));
|
const userAutoCompleteLink = getUserAutoCompleteLink(state);
|
||||||
}
|
const availablePermissions = getAvailablePermissions(state);
|
||||||
};
|
return {
|
||||||
};
|
availablePermissions,
|
||||||
|
namespace,
|
||||||
export default connect(
|
repoName,
|
||||||
mapStateToProps,
|
error,
|
||||||
mapDispatchToProps
|
loading,
|
||||||
)(translate("repos")(Permissions));
|
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));
|
||||||
|
|||||||
@@ -1,176 +1,256 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { Permission } from "@scm-manager/ui-types";
|
import type {
|
||||||
import { translate } from "react-i18next";
|
AvailableRepositoryPermissions,
|
||||||
import {
|
Permission
|
||||||
modifyPermission,
|
} from "@scm-manager/ui-types";
|
||||||
isModifyPermissionPending,
|
import { translate } from "react-i18next";
|
||||||
deletePermission,
|
import {
|
||||||
isDeletePermissionPending
|
modifyPermission,
|
||||||
} from "../modules/permissions";
|
isModifyPermissionPending,
|
||||||
import { connect } from "react-redux";
|
deletePermission,
|
||||||
import type { History } from "history";
|
isDeletePermissionPending,
|
||||||
import { Checkbox } from "@scm-manager/ui-components";
|
findMatchingRoleName
|
||||||
import DeletePermissionButton from "../components/buttons/DeletePermissionButton";
|
} from "../modules/permissions";
|
||||||
import TypeSelector from "../components/TypeSelector";
|
import { connect } from "react-redux";
|
||||||
|
import type { History } from "history";
|
||||||
type Props = {
|
import { Button, Checkbox } from "@scm-manager/ui-components";
|
||||||
submitForm: Permission => void,
|
import DeletePermissionButton from "../components/buttons/DeletePermissionButton";
|
||||||
modifyPermission: (Permission, string, string) => void,
|
import RoleSelector from "../components/RoleSelector";
|
||||||
permission: Permission,
|
import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog";
|
||||||
t: string => string,
|
|
||||||
namespace: string,
|
type Props = {
|
||||||
repoName: string,
|
availablePermissions: AvailableRepositoryPermissions,
|
||||||
match: any,
|
submitForm: Permission => void,
|
||||||
history: History,
|
modifyPermission: (permission: Permission, namespace: string, name: string) => void,
|
||||||
loading: boolean,
|
permission: Permission,
|
||||||
deletePermission: (Permission, string, string) => void,
|
t: string => string,
|
||||||
deleteLoading: boolean
|
namespace: string,
|
||||||
};
|
repoName: string,
|
||||||
|
match: any,
|
||||||
type State = {
|
history: History,
|
||||||
permission: Permission
|
loading: boolean,
|
||||||
};
|
deletePermission: (permission: Permission, namespace: string, name: string) => void,
|
||||||
|
deleteLoading: boolean
|
||||||
class SinglePermission extends React.Component<Props, State> {
|
};
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
type State = {
|
||||||
|
role: string,
|
||||||
this.state = {
|
permission: Permission,
|
||||||
permission: {
|
showAdvancedDialog: boolean
|
||||||
name: "",
|
};
|
||||||
type: "READ",
|
|
||||||
groupPermission: false,
|
class SinglePermission extends React.Component<Props, State> {
|
||||||
_links: {}
|
constructor(props: Props) {
|
||||||
}
|
super(props);
|
||||||
};
|
|
||||||
}
|
const defaultPermission = props.availablePermissions.availableRoles
|
||||||
|
? props.availablePermissions.availableRoles[0]
|
||||||
componentDidMount() {
|
: {};
|
||||||
const { permission } = this.props;
|
|
||||||
if (permission) {
|
this.state = {
|
||||||
this.setState({
|
permission: {
|
||||||
permission: {
|
name: "",
|
||||||
name: permission.name,
|
verbs: defaultPermission.verbs,
|
||||||
type: permission.type,
|
groupPermission: false,
|
||||||
groupPermission: permission.groupPermission,
|
_links: {}
|
||||||
_links: permission._links
|
},
|
||||||
}
|
role: defaultPermission.name,
|
||||||
});
|
showAdvancedDialog: false
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
deletePermission = () => {
|
componentDidMount() {
|
||||||
this.props.deletePermission(
|
const { availablePermissions, permission } = this.props;
|
||||||
this.props.permission,
|
|
||||||
this.props.namespace,
|
const matchingRole = findMatchingRoleName(
|
||||||
this.props.repoName
|
availablePermissions,
|
||||||
);
|
permission.verbs
|
||||||
};
|
);
|
||||||
|
|
||||||
render() {
|
if (permission) {
|
||||||
const { permission } = this.state;
|
this.setState({
|
||||||
const { loading, namespace, repoName } = this.props;
|
permission: {
|
||||||
const typeSelector =
|
name: permission.name,
|
||||||
this.props.permission._links && this.props.permission._links.update ? (
|
verbs: permission.verbs,
|
||||||
<td>
|
groupPermission: permission.groupPermission,
|
||||||
<TypeSelector
|
_links: permission._links
|
||||||
handleTypeChange={this.handleTypeChange}
|
},
|
||||||
type={permission.type ? permission.type : "READ"}
|
role: matchingRole
|
||||||
loading={loading}
|
});
|
||||||
/>
|
}
|
||||||
</td>
|
}
|
||||||
) : (
|
|
||||||
<td>{permission.type}</td>
|
deletePermission = () => {
|
||||||
);
|
this.props.deletePermission(
|
||||||
|
this.props.permission,
|
||||||
return (
|
this.props.namespace,
|
||||||
<tr>
|
this.props.repoName
|
||||||
<td>{permission.name}</td>
|
);
|
||||||
<td>
|
};
|
||||||
<Checkbox checked={permission ? permission.groupPermission : false} />
|
|
||||||
</td>
|
render() {
|
||||||
{typeSelector}
|
const { role, permission, showAdvancedDialog } = this.state;
|
||||||
<td>
|
const {
|
||||||
<DeletePermissionButton
|
t,
|
||||||
permission={permission}
|
availablePermissions,
|
||||||
namespace={namespace}
|
loading,
|
||||||
repoName={repoName}
|
namespace,
|
||||||
deletePermission={this.deletePermission}
|
repoName
|
||||||
loading={this.props.deleteLoading}
|
} = this.props;
|
||||||
/>
|
const availableRoleNames = availablePermissions.availableRoles.map(
|
||||||
</td>
|
r => r.name
|
||||||
</tr>
|
);
|
||||||
);
|
const readOnly = !this.mayChangePermissions();
|
||||||
}
|
const roleSelector = readOnly ? (
|
||||||
|
<td>{role}</td>
|
||||||
handleTypeChange = (type: string) => {
|
) : (
|
||||||
this.setState({
|
<td>
|
||||||
permission: {
|
<RoleSelector
|
||||||
...this.state.permission,
|
handleRoleChange={this.handleRoleChange}
|
||||||
type: type
|
availableRoles={availableRoleNames}
|
||||||
}
|
role={role}
|
||||||
});
|
loading={loading}
|
||||||
this.modifyPermission(type);
|
/>
|
||||||
};
|
</td>
|
||||||
|
);
|
||||||
modifyPermission = (type: string) => {
|
|
||||||
let permission = this.state.permission;
|
const advancedDialg = showAdvancedDialog ? (
|
||||||
permission.type = type;
|
<AdvancedPermissionsDialog
|
||||||
this.props.modifyPermission(
|
readOnly={readOnly}
|
||||||
permission,
|
availableVerbs={availablePermissions.availableVerbs}
|
||||||
this.props.namespace,
|
selectedVerbs={permission.verbs}
|
||||||
this.props.repoName
|
onClose={this.closeAdvancedPermissionsDialog}
|
||||||
);
|
onSubmit={this.submitAdvancedPermissionsDialog}
|
||||||
};
|
/>
|
||||||
|
) : null;
|
||||||
createSelectOptions(types: string[]) {
|
|
||||||
return types.map(type => {
|
return (
|
||||||
return {
|
<tr>
|
||||||
label: type,
|
<td>{permission.name}</td>
|
||||||
value: type
|
<td>
|
||||||
};
|
<Checkbox
|
||||||
});
|
checked={permission ? permission.groupPermission : false}
|
||||||
}
|
disabled={true}
|
||||||
}
|
/>
|
||||||
|
</td>
|
||||||
const mapStateToProps = (state, ownProps) => {
|
{roleSelector}
|
||||||
const permission = ownProps.permission;
|
<td>
|
||||||
const loading = isModifyPermissionPending(
|
<Button
|
||||||
state,
|
label={t("permission.advanced-button.label")}
|
||||||
ownProps.namespace,
|
action={this.handleDetailedPermissionsPressed}
|
||||||
ownProps.repoName,
|
/>
|
||||||
permission
|
</td>
|
||||||
);
|
<td>
|
||||||
const deleteLoading = isDeletePermissionPending(
|
<DeletePermissionButton
|
||||||
state,
|
permission={permission}
|
||||||
ownProps.namespace,
|
namespace={namespace}
|
||||||
ownProps.repoName,
|
repoName={repoName}
|
||||||
permission
|
deletePermission={this.deletePermission}
|
||||||
);
|
loading={this.props.deleteLoading}
|
||||||
|
/>
|
||||||
return { loading, deleteLoading };
|
{advancedDialg}
|
||||||
};
|
</td>
|
||||||
|
</tr>
|
||||||
const mapDispatchToProps = dispatch => {
|
);
|
||||||
return {
|
}
|
||||||
modifyPermission: (
|
|
||||||
permission: Permission,
|
mayChangePermissions = () => {
|
||||||
namespace: string,
|
return this.props.permission._links && this.props.permission._links.update;
|
||||||
repoName: string
|
};
|
||||||
) => {
|
|
||||||
dispatch(modifyPermission(permission, namespace, repoName));
|
handleDetailedPermissionsPressed = () => {
|
||||||
},
|
this.setState({ showAdvancedDialog: true });
|
||||||
deletePermission: (
|
};
|
||||||
permission: Permission,
|
|
||||||
namespace: string,
|
closeAdvancedPermissionsDialog = () => {
|
||||||
repoName: string
|
this.setState({ showAdvancedDialog: false });
|
||||||
) => {
|
};
|
||||||
dispatch(deletePermission(permission, namespace, repoName));
|
|
||||||
}
|
submitAdvancedPermissionsDialog = (newVerbs: string[]) => {
|
||||||
};
|
const { permission } = this.state;
|
||||||
};
|
const newRole = findMatchingRoleName(
|
||||||
export default connect(
|
this.props.availablePermissions,
|
||||||
mapStateToProps,
|
newVerbs
|
||||||
mapDispatchToProps
|
);
|
||||||
)(translate("repos")(SinglePermission));
|
this.setState(
|
||||||
|
{
|
||||||
|
showAdvancedDialog: false,
|
||||||
|
permission: { ...permission, verbs: newVerbs },
|
||||||
|
role: newRole
|
||||||
|
},
|
||||||
|
() => this.modifyPermission(newVerbs)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRoleChange = (role: string) => {
|
||||||
|
const selectedRole = this.findAvailableRole(role);
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
permission: {
|
||||||
|
...this.state.permission,
|
||||||
|
verbs: selectedRole.verbs
|
||||||
|
},
|
||||||
|
role: role
|
||||||
|
},
|
||||||
|
() => this.modifyPermission(selectedRole.verbs)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
findAvailableRole = (roleName: string) => {
|
||||||
|
return this.props.availablePermissions.availableRoles.find(
|
||||||
|
role => role.name === roleName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
modifyPermission = (verbs: string[]) => {
|
||||||
|
let permission = this.state.permission;
|
||||||
|
permission.verbs = verbs;
|
||||||
|
this.props.modifyPermission(
|
||||||
|
permission,
|
||||||
|
this.props.namespace,
|
||||||
|
this.props.repoName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { Action } from "@scm-manager/ui-components";
|
|||||||
import { apiClient } from "@scm-manager/ui-components";
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
import * as types from "../../../modules/types";
|
import * as types from "../../../modules/types";
|
||||||
import type {
|
import type {
|
||||||
|
AvailableRepositoryPermissions,
|
||||||
Permission,
|
Permission,
|
||||||
PermissionCollection,
|
PermissionCollection,
|
||||||
PermissionCreateEntry
|
PermissionCreateEntry
|
||||||
@@ -11,7 +12,18 @@ import type {
|
|||||||
import { isPending } from "../../../modules/pending";
|
import { isPending } from "../../../modules/pending";
|
||||||
import { getFailure } from "../../../modules/failure";
|
import { getFailure } from "../../../modules/failure";
|
||||||
import { Dispatch } from "redux";
|
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 = "scm/permissions/FETCH_PERMISSIONS";
|
||||||
export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${
|
export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${
|
||||||
types.PENDING_SUFFIX
|
types.PENDING_SUFFIX
|
||||||
@@ -62,7 +74,71 @@ export const DELETE_PERMISSION_RESET = `${DELETE_PERMISSION}_${
|
|||||||
types.RESET_SUFFIX
|
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
|
// fetch permissions
|
||||||
|
|
||||||
@@ -368,6 +444,7 @@ export function deletePermissionReset(namespace: string, repoName: string) {
|
|||||||
itemId: namespace + "/" + repoName
|
itemId: namespace + "/" + repoName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function deletePermissionFromState(
|
function deletePermissionFromState(
|
||||||
oldPermissions: PermissionCollection,
|
oldPermissions: PermissionCollection,
|
||||||
permission: Permission
|
permission: Permission
|
||||||
@@ -399,12 +476,17 @@ export default function reducer(
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case FETCH_AVAILABLE_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
available: action.payload
|
||||||
|
};
|
||||||
case FETCH_PERMISSIONS_SUCCESS:
|
case FETCH_PERMISSIONS_SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
[action.itemId]: {
|
[action.itemId]: {
|
||||||
entries: action.payload._embedded.permissions,
|
entries: action.payload._embedded.permissions,
|
||||||
createPermission: action.payload._links.create ? true : false
|
createPermission: !!action.payload._links.create
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
case MODIFY_PERMISSION_SUCCESS:
|
case MODIFY_PERMISSION_SUCCESS:
|
||||||
@@ -452,6 +534,12 @@ export default function reducer(
|
|||||||
|
|
||||||
// selectors
|
// selectors
|
||||||
|
|
||||||
|
export function getAvailablePermissions(state: Object) {
|
||||||
|
if (state.permissions) {
|
||||||
|
return state.permissions.available;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getPermissionsOfRepo(
|
export function getPermissionsOfRepo(
|
||||||
state: Object,
|
state: Object,
|
||||||
namespace: string,
|
namespace: string,
|
||||||
@@ -463,6 +551,10 @@ export function getPermissionsOfRepo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isFetchAvailablePermissionsPending(state: Object) {
|
||||||
|
return isPending(state, FETCH_AVAILABLE, "available");
|
||||||
|
}
|
||||||
|
|
||||||
export function isFetchPermissionsPending(
|
export function isFetchPermissionsPending(
|
||||||
state: Object,
|
state: Object,
|
||||||
namespace: string,
|
namespace: string,
|
||||||
@@ -471,6 +563,10 @@ export function isFetchPermissionsPending(
|
|||||||
return isPending(state, FETCH_PERMISSIONS, namespace + "/" + repoName);
|
return isPending(state, FETCH_PERMISSIONS, namespace + "/" + repoName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFetchAvailablePermissionsFailure(state: Object) {
|
||||||
|
return getFailure(state, FETCH_AVAILABLE, "available");
|
||||||
|
}
|
||||||
|
|
||||||
export function getFetchPermissionsFailure(
|
export function getFetchPermissionsFailure(
|
||||||
state: Object,
|
state: Object,
|
||||||
namespace: string,
|
namespace: string,
|
||||||
@@ -522,6 +618,7 @@ export function isCreatePermissionPending(
|
|||||||
) {
|
) {
|
||||||
return isPending(state, CREATE_PERMISSION, namespace + "/" + repoName);
|
return isPending(state, CREATE_PERMISSION, namespace + "/" + repoName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCreatePermissionFailure(
|
export function getCreatePermissionFailure(
|
||||||
state: Object,
|
state: Object,
|
||||||
namespace: string,
|
namespace: string,
|
||||||
@@ -603,3 +700,33 @@ export function getModifyPermissionsFailure(
|
|||||||
}
|
}
|
||||||
return null;
|
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));
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ const hitchhiker_puzzle42Permission_user_eins: Permission = {
|
|||||||
href:
|
href:
|
||||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
|
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_eins"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
verbs: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const hitchhiker_puzzle42Permission_user_zwei: Permission = {
|
const hitchhiker_puzzle42Permission_user_zwei: Permission = {
|
||||||
@@ -79,7 +80,8 @@ const hitchhiker_puzzle42Permission_user_zwei: Permission = {
|
|||||||
href:
|
href:
|
||||||
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
|
"http://localhost:8081/scm/api/rest/v2/repositories/hitchhiker/puzzle42/permissions/user_zwei"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
verbs: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const hitchhiker_puzzle42Permissions: PermissionCollection = [
|
const hitchhiker_puzzle42Permissions: PermissionCollection = [
|
||||||
@@ -175,8 +177,7 @@ describe("permission fetch", () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins };
|
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||||
editedPermission.type = "OWNER";
|
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
|
|
||||||
@@ -197,8 +198,7 @@ describe("permission fetch", () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins };
|
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||||
editedPermission.type = "OWNER";
|
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
|
|
||||||
@@ -227,8 +227,7 @@ describe("permission fetch", () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins };
|
let editedPermission = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||||
editedPermission.type = "OWNER";
|
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
|
|
||||||
@@ -451,8 +450,7 @@ describe("permissions reducer", () => {
|
|||||||
entries: [hitchhiker_puzzle42Permission_user_eins]
|
entries: [hitchhiker_puzzle42Permission_user_eins]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let permissionEdited = { ...hitchhiker_puzzle42Permission_user_eins };
|
let permissionEdited = { ...hitchhiker_puzzle42Permission_user_eins, type: "OWNER" };
|
||||||
permissionEdited.type = "OWNER";
|
|
||||||
let expectedState = {
|
let expectedState = {
|
||||||
"hitchhiker/puzzle42": {
|
"hitchhiker/puzzle42": {
|
||||||
entries: [permissionEdited]
|
entries: [permissionEdited]
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import sonia.scm.security.RepositoryRole;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class AvailableRepositoryPermissionsDto extends HalRepresentation {
|
||||||
|
private final Collection<String> availableVerbs;
|
||||||
|
private final Collection<RepositoryRole> availableRoles;
|
||||||
|
|
||||||
|
public AvailableRepositoryPermissionsDto(Collection<String> availableVerbs, Collection<RepositoryRole> availableRoles) {
|
||||||
|
this.availableVerbs = availableVerbs;
|
||||||
|
this.availableRoles = availableRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getAvailableVerbs() {
|
||||||
|
return availableVerbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<RepositoryRole> getAvailableRoles() {
|
||||||
|
return availableRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
|
protected HalRepresentation add(Links links) {
|
||||||
|
return super.add(links);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,7 @@ public class IndexDtoGenerator extends LinkAppenderMapper {
|
|||||||
if (PermissionPermissions.list().isPermitted()) {
|
if (PermissionPermissions.list().isPermitted()) {
|
||||||
builder.single(link("permissions", resourceLinks.permissions().self()));
|
builder.single(link("permissions", resourceLinks.permissions().self()));
|
||||||
}
|
}
|
||||||
|
builder.single(link("availableRepositoryPermissions", resourceLinks.availableRepositoryPermissions().self()));
|
||||||
} else {
|
} else {
|
||||||
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class MapperModule extends AbstractModule {
|
|||||||
bind(RepositoryTypeCollectionToDtoMapper.class);
|
bind(RepositoryTypeCollectionToDtoMapper.class);
|
||||||
|
|
||||||
bind(BranchToBranchDtoMapper.class).to(Mappers.getMapper(BranchToBranchDtoMapper.class).getClass());
|
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(RepositoryPermissionToRepositoryPermissionDtoMapper.class).to(Mappers.getMapper(RepositoryPermissionToRepositoryPermissionDtoMapper.class).getClass());
|
||||||
|
|
||||||
bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(ChangesetToChangesetDtoMapper.class).getClass());
|
bind(ChangesetToChangesetDtoMapper.class).to(Mappers.getMapper(ChangesetToChangesetDtoMapper.class).getClass());
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
@@ -24,6 +23,7 @@ import javax.ws.rs.Produces;
|
|||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
public class RepositoryCollectionResource {
|
public class RepositoryCollectionResource {
|
||||||
@@ -100,7 +100,7 @@ public class RepositoryCollectionResource {
|
|||||||
|
|
||||||
private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) {
|
private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) {
|
||||||
Repository repository = dtoToRepositoryMapper.map(repositoryDto, null);
|
Repository repository = dtoToRepositoryMapper.map(repositoryDto, null);
|
||||||
repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), PermissionType.OWNER)));
|
repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), singletonList("*"), false)));
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ public abstract class RepositoryDtoToRepositoryMapper extends BaseDtoMapper {
|
|||||||
@Mapping(target = "id", ignore = true)
|
@Mapping(target = "id", ignore = true)
|
||||||
@Mapping(target = "publicReadable", ignore = true)
|
@Mapping(target = "publicReadable", ignore = true)
|
||||||
@Mapping(target = "healthCheckFailures", ignore = true)
|
@Mapping(target = "healthCheckFailures", ignore = true)
|
||||||
@Mapping(target = "permissions", ignore = true)
|
|
||||||
public abstract Repository map(RepositoryDto repositoryDto, @Context String id);
|
public abstract Repository map(RepositoryDto repositoryDto, @Context String id);
|
||||||
|
|
||||||
@AfterMapping
|
@AfterMapping
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
import javax.validation.constraints.Pattern;
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
|
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
|
||||||
|
|
||||||
@Getter @Setter @ToString @NoArgsConstructor
|
@Getter @Setter @ToString @NoArgsConstructor
|
||||||
@@ -20,16 +24,8 @@ public class RepositoryPermissionDto extends HalRepresentation {
|
|||||||
@Pattern(regexp = USER_GROUP_PATTERN)
|
@Pattern(regexp = USER_GROUP_PATTERN)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/**
|
@NotEmpty
|
||||||
* the type can be replaced with a dto enum if the mapstruct 1.3.0 is stable
|
private Collection<String> verbs;
|
||||||
* 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 boolean groupPermission = false;
|
private boolean groupPermission = false;
|
||||||
|
|
||||||
@@ -38,7 +34,6 @@ public class RepositoryPermissionDto extends HalRepresentation {
|
|||||||
this.groupPermission = groupPermission;
|
this.groupPermission = groupPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
protected HalRepresentation add(Links links) {
|
protected HalRepresentation add(Links links) {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import org.mapstruct.CollectionMappingStrategy;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
|
||||||
@Mapper
|
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE)
|
||||||
public abstract class PermissionDtoToPermissionMapper {
|
public abstract class RepositoryPermissionDtoToRepositoryPermissionMapper {
|
||||||
|
|
||||||
public abstract RepositoryPermission map(RepositoryPermissionDto permissionDto);
|
public abstract RepositoryPermission map(RepositoryPermissionDto permissionDto);
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import sonia.scm.security.RepositoryPermissionProvider;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RESTful Web Service Resource to get available repository types.
|
||||||
|
*/
|
||||||
|
@Path(RepositoryPermissionResource.PATH)
|
||||||
|
public class RepositoryPermissionResource {
|
||||||
|
|
||||||
|
static final String PATH = "v2/repositoryPermissions/";
|
||||||
|
|
||||||
|
private final RepositoryPermissionProvider repositoryPermissionProvider;
|
||||||
|
private final ResourceLinks resourceLinks;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RepositoryPermissionResource(RepositoryPermissionProvider repositoryPermissionProvider, ResourceLinks resourceLinks) {
|
||||||
|
this.repositoryPermissionProvider = repositoryPermissionProvider;
|
||||||
|
this.resourceLinks = resourceLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("")
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
@Produces(VndMediaType.REPOSITORY_PERMISSION_COLLECTION)
|
||||||
|
public AvailableRepositoryPermissionsDto get() {
|
||||||
|
AvailableRepositoryPermissionsDto dto = new AvailableRepositoryPermissionsDto(repositoryPermissionProvider.availableVerbs(), repositoryPermissionProvider.availableRoles());
|
||||||
|
dto.add(Links.linkingTo().self(resourceLinks.availableRepositoryPermissions().self()).build());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,18 +35,21 @@ import static sonia.scm.NotFoundException.notFound;
|
|||||||
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
|
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class PermissionRootResource {
|
public class RepositoryPermissionRootResource {
|
||||||
|
|
||||||
|
private RepositoryPermissionDtoToRepositoryPermissionMapper dtoToModelMapper;
|
||||||
private PermissionDtoToPermissionMapper dtoToModelMapper;
|
|
||||||
private RepositoryPermissionToRepositoryPermissionDtoMapper modelToDtoMapper;
|
private RepositoryPermissionToRepositoryPermissionDtoMapper modelToDtoMapper;
|
||||||
private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper;
|
private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper;
|
||||||
private ResourceLinks resourceLinks;
|
private ResourceLinks resourceLinks;
|
||||||
private final RepositoryManager manager;
|
private final RepositoryManager manager;
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@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.dtoToModelMapper = dtoToModelMapper;
|
||||||
this.modelToDtoMapper = modelToDtoMapper;
|
this.modelToDtoMapper = modelToDtoMapper;
|
||||||
this.repositoryPermissionCollectionToDtoMapper = repositoryPermissionCollectionToDtoMapper;
|
this.repositoryPermissionCollectionToDtoMapper = repositoryPermissionCollectionToDtoMapper;
|
||||||
@@ -54,7 +57,6 @@ public class PermissionRootResource {
|
|||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new permission to the user or group managed by the repository
|
* Adds a new permission to the user or group managed by the repository
|
||||||
*
|
*
|
||||||
@@ -71,9 +73,9 @@ public class PermissionRootResource {
|
|||||||
@ResponseCode(code = 409, condition = "conflict")
|
@ResponseCode(code = 409, condition = "conflict")
|
||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
@Consumes(VndMediaType.PERMISSION)
|
@Consumes(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
@Path("")
|
@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);
|
log.info("try to add new permission: {}", permission);
|
||||||
Repository repository = load(namespace, name);
|
Repository repository = load(namespace, name);
|
||||||
RepositoryPermissions.permissionWrite(repository).check();
|
RepositoryPermissions.permissionWrite(repository).check();
|
||||||
@@ -84,7 +86,6 @@ public class PermissionRootResource {
|
|||||||
return Response.created(URI.create(resourceLinks.repositoryPermission().self(namespace, name, urlPermissionName))).build();
|
return Response.created(URI.create(resourceLinks.repositoryPermission().self(namespace, name, urlPermissionName))).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the searched permission with permission name related to a repository
|
* Get the searched permission with permission name related to a repository
|
||||||
*
|
*
|
||||||
@@ -99,7 +100,7 @@ public class PermissionRootResource {
|
|||||||
@ResponseCode(code = 404, condition = "not found"),
|
@ResponseCode(code = 404, condition = "not found"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@Produces(VndMediaType.PERMISSION)
|
@Produces(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
@TypeHint(RepositoryPermissionDto.class)
|
@TypeHint(RepositoryPermissionDto.class)
|
||||||
@Path("{permission-name}")
|
@Path("{permission-name}")
|
||||||
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName) {
|
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("permission-name") String permissionName) {
|
||||||
@@ -115,7 +116,6 @@ public class PermissionRootResource {
|
|||||||
).build();
|
).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all permissions related to a repository
|
* Get all permissions related to a repository
|
||||||
*
|
*
|
||||||
@@ -130,7 +130,7 @@ public class PermissionRootResource {
|
|||||||
@ResponseCode(code = 404, condition = "not found"),
|
@ResponseCode(code = 404, condition = "not found"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@Produces(VndMediaType.PERMISSION)
|
@Produces(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
@TypeHint(RepositoryPermissionDto.class)
|
@TypeHint(RepositoryPermissionDto.class)
|
||||||
@Path("")
|
@Path("")
|
||||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) {
|
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) {
|
||||||
@@ -139,7 +139,6 @@ public class PermissionRootResource {
|
|||||||
return Response.ok(repositoryPermissionCollectionToDtoMapper.map(repository)).build();
|
return Response.ok(repositoryPermissionCollectionToDtoMapper.map(repository)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a permission to the user or group managed by the repository
|
* 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)
|
* ignore the user input for groupPermission and take it from the path parameter (if the group prefix (@) exists it is a group permission)
|
||||||
@@ -155,7 +154,7 @@ public class PermissionRootResource {
|
|||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
@Consumes(VndMediaType.PERMISSION)
|
@Consumes(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
@Path("{permission-name}")
|
@Path("{permission-name}")
|
||||||
public Response update(@PathParam("namespace") String namespace,
|
public Response update(@PathParam("namespace") String namespace,
|
||||||
@PathParam("name") String name,
|
@PathParam("name") String name,
|
||||||
@@ -172,6 +171,7 @@ public class PermissionRootResource {
|
|||||||
if (!extractedPermissionName.equals(permission.getName())) {
|
if (!extractedPermissionName.equals(permission.getName())) {
|
||||||
checkPermissionAlreadyExists(permission, repository);
|
checkPermissionAlreadyExists(permission, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
RepositoryPermission existingPermission = repository.getPermissions()
|
RepositoryPermission existingPermission = repository.getPermissions()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(filterPermission(permissionName))
|
.filter(filterPermission(permissionName))
|
||||||
@@ -208,17 +208,16 @@ public class PermissionRootResource {
|
|||||||
.stream()
|
.stream()
|
||||||
.filter(filterPermission(permissionName))
|
.filter(filterPermission(permissionName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.ifPresent(repository::removePermission)
|
.ifPresent(repository::removePermission);
|
||||||
;
|
|
||||||
manager.modify(repository);
|
manager.modify(repository);
|
||||||
log.info("the permission with name: {} is updated.", permissionName);
|
log.info("the permission with name: {} is updated.", permissionName);
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate<RepositoryPermission> filterPermission(String permissionName) {
|
private Predicate<RepositoryPermission> filterPermission(String name) {
|
||||||
return permission -> getPermissionName(permissionName).equals(permission.getName())
|
return permission -> getPermissionName(name).equals(permission.getName())
|
||||||
&&
|
&&
|
||||||
permission.isGroupPermission() == isGroupPermission(permissionName);
|
permission.isGroupPermission() == isGroupPermission(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPermissionName(String permissionName) {
|
private String getPermissionName(String permissionName) {
|
||||||
@@ -231,7 +230,6 @@ public class PermissionRootResource {
|
|||||||
return permissionName.startsWith(GROUP_PREFIX);
|
return permissionName.startsWith(GROUP_PREFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check if the actual user is permitted to manage the repository permissions
|
* check if the actual user is permitted to manage the repository permissions
|
||||||
* return the repository if the user is permitted
|
* return the repository if the user is permitted
|
||||||
@@ -39,7 +39,7 @@ public class RepositoryResource {
|
|||||||
private final Provider<ChangesetRootResource> changesetRootResource;
|
private final Provider<ChangesetRootResource> changesetRootResource;
|
||||||
private final Provider<SourceRootResource> sourceRootResource;
|
private final Provider<SourceRootResource> sourceRootResource;
|
||||||
private final Provider<ContentResource> contentResource;
|
private final Provider<ContentResource> contentResource;
|
||||||
private final Provider<PermissionRootResource> permissionRootResource;
|
private final Provider<RepositoryPermissionRootResource> permissionRootResource;
|
||||||
private final Provider<DiffRootResource> diffRootResource;
|
private final Provider<DiffRootResource> diffRootResource;
|
||||||
private final Provider<ModificationsRootResource> modificationsRootResource;
|
private final Provider<ModificationsRootResource> modificationsRootResource;
|
||||||
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
|
private final Provider<FileHistoryRootResource> fileHistoryRootResource;
|
||||||
@@ -54,7 +54,7 @@ public class RepositoryResource {
|
|||||||
Provider<BranchRootResource> branchRootResource,
|
Provider<BranchRootResource> branchRootResource,
|
||||||
Provider<ChangesetRootResource> changesetRootResource,
|
Provider<ChangesetRootResource> changesetRootResource,
|
||||||
Provider<SourceRootResource> sourceRootResource, Provider<ContentResource> contentResource,
|
Provider<SourceRootResource> sourceRootResource, Provider<ContentResource> contentResource,
|
||||||
Provider<PermissionRootResource> permissionRootResource,
|
Provider<RepositoryPermissionRootResource> permissionRootResource,
|
||||||
Provider<DiffRootResource> diffRootResource,
|
Provider<DiffRootResource> diffRootResource,
|
||||||
Provider<ModificationsRootResource> modificationsRootResource,
|
Provider<ModificationsRootResource> modificationsRootResource,
|
||||||
Provider<FileHistoryRootResource> fileHistoryRootResource,
|
Provider<FileHistoryRootResource> fileHistoryRootResource,
|
||||||
@@ -154,7 +154,6 @@ public class RepositoryResource {
|
|||||||
|
|
||||||
private Repository processUpdate(RepositoryDto repositoryDto, Repository existing) {
|
private Repository processUpdate(RepositoryDto repositoryDto, Repository existing) {
|
||||||
Repository changedRepository = dtoToRepositoryMapper.map(repositoryDto, existing.getId());
|
Repository changedRepository = dtoToRepositoryMapper.map(repositoryDto, existing.getId());
|
||||||
changedRepository.setPermissions(existing.getPermissions());
|
|
||||||
return changedRepository;
|
return changedRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +193,7 @@ public class RepositoryResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Path("permissions/")
|
@Path("permissions/")
|
||||||
public PermissionRootResource permissions() {
|
public RepositoryPermissionRootResource permissions() {
|
||||||
return permissionRootResource.get();
|
return permissionRootResource.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ class ResourceLinks {
|
|||||||
private final LinkBuilder permissionLinkBuilder;
|
private final LinkBuilder permissionLinkBuilder;
|
||||||
|
|
||||||
RepositoryPermissionLinks(ScmPathInfo pathInfo) {
|
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) {
|
String all(String namespace, String name) {
|
||||||
@@ -639,14 +639,30 @@ class ResourceLinks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static class PermissionsLinks {
|
static class PermissionsLinks {
|
||||||
private final LinkBuilder permissionsLlinkBuilder;
|
private final LinkBuilder permissionsLinkBuilder;
|
||||||
|
|
||||||
PermissionsLinks(ScmPathInfo scmPathInfo) {
|
PermissionsLinks(ScmPathInfo scmPathInfo) {
|
||||||
this.permissionsLlinkBuilder = new LinkBuilder(scmPathInfo, GlobalPermissionResource.class);
|
this.permissionsLinkBuilder = new LinkBuilder(scmPathInfo, GlobalPermissionResource.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
String self() {
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,8 +198,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
|||||||
private void collectRepositoryPermissions(Builder<String> builder,
|
private void collectRepositoryPermissions(Builder<String> builder,
|
||||||
Repository repository, User user, GroupNames groups)
|
Repository repository, User user, GroupNames groups)
|
||||||
{
|
{
|
||||||
Collection<RepositoryPermission> repositoryPermissions
|
Collection<RepositoryPermission> repositoryPermissions = repository.getPermissions();
|
||||||
= repository.getPermissions();
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(repositoryPermissions))
|
if (Util.isNotEmpty(repositoryPermissions))
|
||||||
{
|
{
|
||||||
@@ -207,9 +206,9 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
|||||||
for (RepositoryPermission permission : repositoryPermissions)
|
for (RepositoryPermission permission : repositoryPermissions)
|
||||||
{
|
{
|
||||||
hasPermission = isUserPermitted(user, groups, permission);
|
hasPermission = isUserPermitted(user, groups, permission);
|
||||||
if (hasPermission)
|
if (hasPermission && !permission.getVerbs().isEmpty())
|
||||||
{
|
{
|
||||||
String perm = permission.getType().getPermissionPrefix().concat(repository.getId());
|
String perm = "repository:" + String.join(",", permission.getVerbs()) + ":" + repository.getId();
|
||||||
if (logger.isTraceEnabled())
|
if (logger.isTraceEnabled())
|
||||||
{
|
{
|
||||||
logger.trace("add repository permission {} for user {} at repository {}",
|
logger.trace("add repository permission {} for user {} at repository {}",
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.plugin.PluginLoader;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.util.Collections.unmodifiableCollection;
|
||||||
|
|
||||||
|
public class RepositoryPermissionProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RepositoryPermissionProvider.class);
|
||||||
|
private static final String REPOSITORY_PERMISSION_DESCRIPTOR = "META-INF/scm/repository-permissions.xml";
|
||||||
|
private final Collection<String> availableVerbs;
|
||||||
|
private final Collection<RepositoryRole> availableRoles;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RepositoryPermissionProvider(PluginLoader pluginLoader) {
|
||||||
|
AvailableRepositoryPermissions availablePermissions = readAvailablePermissions(pluginLoader);
|
||||||
|
this.availableVerbs = unmodifiableCollection(new LinkedHashSet<>(availablePermissions.availableVerbs));
|
||||||
|
this.availableRoles = unmodifiableCollection(new LinkedHashSet<>(availablePermissions.availableRoles.stream().map(r -> new RepositoryRole(r.name, r.verbs.verbs)).collect(Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> availableVerbs() {
|
||||||
|
return availableVerbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<RepositoryRole> availableRoles() {
|
||||||
|
return availableRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AvailableRepositoryPermissions readAvailablePermissions(PluginLoader pluginLoader) {
|
||||||
|
Collection<String> availableVerbs = new ArrayList<>();
|
||||||
|
Collection<RoleDescriptor> availableRoles = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
JAXBContext context =
|
||||||
|
JAXBContext.newInstance(RepositoryPermissionsRoot.class);
|
||||||
|
|
||||||
|
// Querying permissions from uberClassLoader returns also the permissions from plugin
|
||||||
|
Enumeration<URL> descriptorEnum =
|
||||||
|
pluginLoader.getUberClassLoader().getResources(REPOSITORY_PERMISSION_DESCRIPTOR);
|
||||||
|
|
||||||
|
while (descriptorEnum.hasMoreElements()) {
|
||||||
|
URL descriptorUrl = descriptorEnum.nextElement();
|
||||||
|
|
||||||
|
logger.debug("read repository permission descriptor from {}", descriptorUrl);
|
||||||
|
|
||||||
|
RepositoryPermissionsRoot repositoryPermissionsRoot = parsePermissionDescriptor(context, descriptorUrl);
|
||||||
|
availableVerbs.addAll(repositoryPermissionsRoot.verbs.verbs);
|
||||||
|
availableRoles.addAll(repositoryPermissionsRoot.roles.roles);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.error("could not read permission descriptors", ex);
|
||||||
|
} catch (JAXBException ex) {
|
||||||
|
logger.error(
|
||||||
|
"could not create jaxb context to read permission descriptors", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AvailableRepositoryPermissions(availableVerbs, availableRoles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static RepositoryPermissionsRoot parsePermissionDescriptor(JAXBContext context, URL descriptorUrl) {
|
||||||
|
try {
|
||||||
|
RepositoryPermissionsRoot descriptorWrapper =
|
||||||
|
(RepositoryPermissionsRoot) context.createUnmarshaller().unmarshal(
|
||||||
|
descriptorUrl);
|
||||||
|
logger.trace("repository permissions from {}: {}", descriptorUrl, descriptorWrapper.verbs.verbs);
|
||||||
|
logger.trace("repository roles from {}: {}", descriptorUrl, descriptorWrapper.roles.roles);
|
||||||
|
return descriptorWrapper;
|
||||||
|
} catch (JAXBException ex) {
|
||||||
|
logger.error("could not parse permission descriptor", ex);
|
||||||
|
return new RepositoryPermissionsRoot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AvailableRepositoryPermissions {
|
||||||
|
private final Collection<String> availableVerbs;
|
||||||
|
private final Collection<RoleDescriptor> availableRoles;
|
||||||
|
|
||||||
|
private AvailableRepositoryPermissions(Collection<String> availableVerbs, Collection<RoleDescriptor> availableRoles) {
|
||||||
|
this.availableVerbs = unmodifiableCollection(availableVerbs);
|
||||||
|
this.availableRoles = unmodifiableCollection(availableRoles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlRootElement(name = "repository-permissions")
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
private static class RepositoryPermissionsRoot {
|
||||||
|
private VerbListDescriptor verbs = new VerbListDescriptor();
|
||||||
|
private RoleListDescriptor roles = new RoleListDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlRootElement(name = "verbs")
|
||||||
|
private static class VerbListDescriptor {
|
||||||
|
@XmlElement(name = "verb")
|
||||||
|
private List<String> verbs = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlRootElement(name = "roles")
|
||||||
|
private static class RoleListDescriptor {
|
||||||
|
@XmlElement(name = "role")
|
||||||
|
private List<RoleDescriptor> roles = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlRootElement(name = "role")
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public static class RoleDescriptor {
|
||||||
|
@XmlElement(name = "name")
|
||||||
|
private String name;
|
||||||
|
@XmlElement(name = "verbs")
|
||||||
|
private VerbListDescriptor verbs = new VerbListDescriptor();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class RepositoryRole {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final Collection<String> verbs;
|
||||||
|
|
||||||
|
public RepositoryRole(String name, Collection<String> verbs) {
|
||||||
|
this.name = name;
|
||||||
|
this.verbs = verbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getVerbs() {
|
||||||
|
return Collections.unmodifiableCollection(verbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "Role " + name + " (" + String.join(", ", verbs) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof RepositoryRole)) return false;
|
||||||
|
RepositoryRole that = (RepositoryRole) o;
|
||||||
|
return name.equals(that.name) &&
|
||||||
|
CollectionUtils.isEqualCollection(this.verbs, that.verbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, verbs.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import com.google.inject.Inject;
|
|||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.shiro.authz.AuthorizationException;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.PushStateDispatcher;
|
import sonia.scm.PushStateDispatcher;
|
||||||
import sonia.scm.filter.WebElement;
|
import sonia.scm.filter.WebElement;
|
||||||
@@ -74,6 +75,9 @@ public class HttpProtocolServlet extends HttpServlet {
|
|||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
log.debug(e.getMessage());
|
log.debug(e.getMessage());
|
||||||
resp.setStatus(HttpStatus.SC_NOT_FOUND);
|
resp.setStatus(HttpStatus.SC_NOT_FOUND);
|
||||||
|
} catch (AuthorizationException e) {
|
||||||
|
log.debug(e.getMessage());
|
||||||
|
resp.setStatus(HttpStatus.SC_FORBIDDEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<repository-permissions>
|
||||||
|
<verbs>
|
||||||
|
<verb>read</verb>
|
||||||
|
<verb>modify</verb>
|
||||||
|
<verb>delete</verb>
|
||||||
|
<verb>pull</verb>
|
||||||
|
<verb>push</verb>
|
||||||
|
<verb>permissionRead</verb>
|
||||||
|
<verb>permissionWrite</verb>
|
||||||
|
<verb>healthCheck</verb>
|
||||||
|
<verb>*</verb>
|
||||||
|
</verbs>
|
||||||
|
<roles>
|
||||||
|
<role>
|
||||||
|
<name>READ</name>
|
||||||
|
<verbs>
|
||||||
|
<verb>read</verb>
|
||||||
|
<verb>pull</verb>
|
||||||
|
</verbs>
|
||||||
|
</role>
|
||||||
|
<role>
|
||||||
|
<name>WRITE</name>
|
||||||
|
<verbs>
|
||||||
|
<verb>read</verb>
|
||||||
|
<verb>pull</verb>
|
||||||
|
<verb>push</verb>
|
||||||
|
</verbs>
|
||||||
|
</role>
|
||||||
|
<role>
|
||||||
|
<name>HEALTH</name>
|
||||||
|
<verbs>
|
||||||
|
<verb>healthCheck</verb>
|
||||||
|
</verbs>
|
||||||
|
</role>
|
||||||
|
<role>
|
||||||
|
<name>OWNER</name>
|
||||||
|
<verbs>
|
||||||
|
<verb>*</verb>
|
||||||
|
</verbs>
|
||||||
|
</role>
|
||||||
|
</roles>
|
||||||
|
</repository-permissions>
|
||||||
@@ -37,5 +37,45 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"unknown": "Unknown permission"
|
"unknown": "Unknown permission"
|
||||||
|
},
|
||||||
|
"verbs": {
|
||||||
|
"repository": {
|
||||||
|
"read": {
|
||||||
|
"displayName": "read",
|
||||||
|
"description": "May see the repository inside the SCM-Manager"
|
||||||
|
},
|
||||||
|
"modify": {
|
||||||
|
"displayName": "modify",
|
||||||
|
"description": "May modify the properties of the repository"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"displayName": "delete",
|
||||||
|
"description": "May delete the repository"
|
||||||
|
},
|
||||||
|
"pull": {
|
||||||
|
"displayName": "pull/checkout",
|
||||||
|
"description": "May pull/checkout the repository"
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"displayName": "push/commit",
|
||||||
|
"description": "May change the content of the repository (push/commit)"
|
||||||
|
},
|
||||||
|
"permissionRead": {
|
||||||
|
"displayName": "read permissions",
|
||||||
|
"description": "May see the permissions of the repository"
|
||||||
|
},
|
||||||
|
"permissionWrite": {
|
||||||
|
"displayName": "modify permissions",
|
||||||
|
"description": "May modify the permissions of the repository"
|
||||||
|
},
|
||||||
|
"healthCheck": {
|
||||||
|
"displayName": "health check",
|
||||||
|
"description": "May run the health check for the repository"
|
||||||
|
},
|
||||||
|
"*": {
|
||||||
|
"displayName": "overall",
|
||||||
|
"description": "May change everything for the repository (includes all other permissions)"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
@@ -29,10 +30,9 @@ import org.junit.jupiter.api.TestFactory;
|
|||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -47,6 +47,8 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
import static de.otto.edison.hal.Link.link;
|
import static de.otto.edison.hal.Link.link;
|
||||||
import static de.otto.edison.hal.Links.linkingTo;
|
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.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
@@ -66,7 +68,7 @@ import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
|
|||||||
password = "secret",
|
password = "secret",
|
||||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
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_NAMESPACE = "repo_namespace";
|
||||||
private static final String REPOSITORY_NAME = "repo";
|
private static final String REPOSITORY_NAME = "repo";
|
||||||
private static final String PERMISSION_WRITE = "repository:permissionWrite:" + REPOSITORY_NAME;
|
private static final String PERMISSION_WRITE = "repository:permissionWrite:" + REPOSITORY_NAME;
|
||||||
@@ -76,15 +78,15 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
private static final String PERMISSION_NAME = "perm";
|
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_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 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<RepositoryPermission> TEST_PERMISSIONS = Lists
|
private static final ArrayList<RepositoryPermission> TEST_PERMISSIONS = Lists
|
||||||
.newArrayList(
|
.newArrayList(
|
||||||
new RepositoryPermission("user_write", PermissionType.WRITE, false),
|
new RepositoryPermission("user_write", asList("read","modify"), false),
|
||||||
new RepositoryPermission("user_read", PermissionType.READ, false),
|
new RepositoryPermission("user_read", singletonList("read"), false),
|
||||||
new RepositoryPermission("user_owner", PermissionType.OWNER, false),
|
new RepositoryPermission("user_owner", singletonList("*"), false),
|
||||||
new RepositoryPermission("group_read", PermissionType.READ, true),
|
new RepositoryPermission("group_read", singletonList("read"), true),
|
||||||
new RepositoryPermission("group_write", PermissionType.WRITE, true),
|
new RepositoryPermission("group_write", asList("read","modify"), true),
|
||||||
new RepositoryPermission("group_owner", PermissionType.OWNER, true)
|
new RepositoryPermission("group_owner", singletonList("*"), true)
|
||||||
);
|
);
|
||||||
private final ExpectedRequest requestGETAllPermissions = new ExpectedRequest()
|
private final ExpectedRequest requestGETAllPermissions = new ExpectedRequest()
|
||||||
.description("GET all permissions")
|
.description("GET all permissions")
|
||||||
@@ -124,11 +126,11 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
private RepositoryPermissionToRepositoryPermissionDtoMapperImpl permissionToPermissionDtoMapper;
|
private RepositoryPermissionToRepositoryPermissionDtoMapperImpl permissionToPermissionDtoMapper;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private PermissionDtoToPermissionMapperImpl permissionDtoToPermissionMapper;
|
private RepositoryPermissionDtoToRepositoryPermissionMapperImpl permissionDtoToPermissionMapper;
|
||||||
|
|
||||||
private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper;
|
private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper;
|
||||||
|
|
||||||
private PermissionRootResource permissionRootResource;
|
private RepositoryPermissionRootResource repositoryPermissionRootResource;
|
||||||
|
|
||||||
private final Subject subject = mock(Subject.class);
|
private final Subject subject = mock(Subject.class);
|
||||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||||
@@ -138,8 +140,8 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
public void prepareEnvironment() {
|
public void prepareEnvironment() {
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
repositoryPermissionCollectionToDtoMapper = new RepositoryPermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks);
|
repositoryPermissionCollectionToDtoMapper = new RepositoryPermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks);
|
||||||
permissionRootResource = new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, repositoryPermissionCollectionToDtoMapper, resourceLinks, repositoryManager);
|
repositoryPermissionRootResource = new RepositoryPermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, repositoryPermissionCollectionToDtoMapper, resourceLinks, repositoryManager);
|
||||||
super.permissionRootResource = Providers.of(permissionRootResource);
|
super.permissionRootResource = Providers.of(repositoryPermissionRootResource);
|
||||||
dispatcher = createDispatcher(getRepositoryRootResource());
|
dispatcher = createDispatcher(getRepositoryRootResource());
|
||||||
subjectThreadState.bind();
|
subjectThreadState.bind();
|
||||||
ThreadContext.bind(subject);
|
ThreadContext.bind(subject);
|
||||||
@@ -232,11 +234,11 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
public void shouldGet400OnCreatingNewPermissionWithNotAllowedCharacters() throws URISyntaxException {
|
public void shouldGet400OnCreatingNewPermissionWithNotAllowedCharacters() throws URISyntaxException {
|
||||||
// the @ character at the begin of the name is not allowed
|
// the @ character at the begin of the name is not allowed
|
||||||
createUserWithRepository("user");
|
createUserWithRepository("user");
|
||||||
String permissionJson = "{ \"name\": \"@permission\", \"type\": \"OWNER\" }";
|
String permissionJson = "{ \"name\": \"@permission\", \"verbs\": [\"*\"] }";
|
||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
||||||
.content(permissionJson.getBytes())
|
.content(permissionJson.getBytes())
|
||||||
.contentType(VndMediaType.PERMISSION);
|
.contentType(VndMediaType.REPOSITORY_PERMISSION);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
@@ -244,11 +246,11 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
assertEquals(400, response.getStatus());
|
assertEquals(400, response.getStatus());
|
||||||
|
|
||||||
// the whitespace at the begin opf the name is not allowed
|
// the whitespace at the begin opf the name is not allowed
|
||||||
permissionJson = "{ \"name\": \" permission\", \"type\": \"OWNER\" }";
|
permissionJson = "{ \"name\": \" permission\", \"verbs\": [\"*\"] }";
|
||||||
request = MockHttpRequest
|
request = MockHttpRequest
|
||||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
||||||
.content(permissionJson.getBytes())
|
.content(permissionJson.getBytes())
|
||||||
.contentType(VndMediaType.PERMISSION);
|
.contentType(VndMediaType.REPOSITORY_PERMISSION);
|
||||||
response = new MockHttpResponse();
|
response = new MockHttpResponse();
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
@@ -259,12 +261,12 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldGetCreatedPermissions() throws URISyntaxException {
|
public void shouldGetCreatedPermissions() throws URISyntaxException {
|
||||||
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
||||||
RepositoryPermission newPermission = new RepositoryPermission("new_group_perm", PermissionType.WRITE, true);
|
RepositoryPermission newPermission = new RepositoryPermission("new_group_perm", asList("read", "pull", "push"), true);
|
||||||
ArrayList<RepositoryPermission> permissions = Lists.newArrayList(TEST_PERMISSIONS);
|
ArrayList<RepositoryPermission> permissions = Lists.newArrayList(TEST_PERMISSIONS);
|
||||||
permissions.add(newPermission);
|
permissions.add(newPermission);
|
||||||
ImmutableList<RepositoryPermission> expectedPermissions = ImmutableList.copyOf(permissions);
|
ImmutableList<RepositoryPermission> expectedPermissions = ImmutableList.copyOf(permissions);
|
||||||
assertExpectedRequest(requestPOSTPermission
|
assertExpectedRequest(requestPOSTPermission
|
||||||
.content("{\"name\" : \"" + newPermission.getName() + "\" , \"type\" : \"WRITE\" , \"groupPermission\" : true}")
|
.content("{\"name\" : \"" + newPermission.getName() + "\" , \"verbs\" : [\"read\",\"pull\",\"push\"], \"groupPermission\" : true}")
|
||||||
.expectedResponseStatus(201)
|
.expectedResponseStatus(201)
|
||||||
.responseValidator(response -> assertThat(response.getContentAsString())
|
.responseValidator(response -> assertThat(response.getContentAsString())
|
||||||
.as("POST response has no body")
|
.as("POST response has no body")
|
||||||
@@ -278,7 +280,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
||||||
RepositoryPermission newPermission = TEST_PERMISSIONS.get(0);
|
RepositoryPermission newPermission = TEST_PERMISSIONS.get(0);
|
||||||
assertExpectedRequest(requestPOSTPermission
|
assertExpectedRequest(requestPOSTPermission
|
||||||
.content("{\"name\" : \"" + newPermission.getName() + "\" , \"type\" : \"WRITE\" , \"groupPermission\" : false}")
|
.content("{\"name\" : \"" + newPermission.getName() + "\" , \"verbs\" : [\"read\",\"pull\",\"push\"], \"groupPermission\" : false}")
|
||||||
.expectedResponseStatus(409)
|
.expectedResponseStatus(409)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -288,10 +290,10 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
||||||
RepositoryPermission modifiedPermission = TEST_PERMISSIONS.get(0);
|
RepositoryPermission modifiedPermission = TEST_PERMISSIONS.get(0);
|
||||||
// modify the type to owner
|
// modify the type to owner
|
||||||
modifiedPermission.setType(PermissionType.OWNER);
|
modifiedPermission.setVerbs(new ArrayList<>(singletonList("*")));
|
||||||
ImmutableList<RepositoryPermission> expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS);
|
ImmutableList<RepositoryPermission> expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS);
|
||||||
assertExpectedRequest(requestPUTPermission
|
assertExpectedRequest(requestPUTPermission
|
||||||
.content("{\"name\" : \"" + modifiedPermission.getName() + "\" , \"type\" : \"OWNER\" , \"groupPermission\" : false}")
|
.content("{\"name\" : \"" + modifiedPermission.getName() + "\" , \"verbs\" : [\"*\"], \"groupPermission\" : false}")
|
||||||
.path(PATH_OF_ALL_PERMISSIONS + modifiedPermission.getName())
|
.path(PATH_OF_ALL_PERMISSIONS + modifiedPermission.getName())
|
||||||
.expectedResponseStatus(204)
|
.expectedResponseStatus(204)
|
||||||
.responseValidator(response -> assertThat(response.getContentAsString())
|
.responseValidator(response -> assertThat(response.getContentAsString())
|
||||||
@@ -353,7 +355,10 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
.map(hal -> {
|
.map(hal -> {
|
||||||
RepositoryPermissionDto result = new RepositoryPermissionDto();
|
RepositoryPermissionDto result = new RepositoryPermissionDto();
|
||||||
result.setName(hal.getAttribute("name").asText());
|
result.setName(hal.getAttribute("name").asText());
|
||||||
result.setType(hal.getAttribute("type").asText());
|
JsonNode attribute = hal.getAttribute("verbs");
|
||||||
|
List<String> verbs = new ArrayList<>();
|
||||||
|
attribute.iterator().forEachRemaining(v -> verbs.add(v.asText()));
|
||||||
|
result.setVerbs(verbs);
|
||||||
result.setGroupPermission(hal.getAttribute("groupPermission").asBoolean());
|
result.setGroupPermission(hal.getAttribute("groupPermission").asBoolean());
|
||||||
result.add(hal.getLinks());
|
result.add(hal.getLinks());
|
||||||
return result;
|
return result;
|
||||||
@@ -382,7 +387,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
RepositoryPermissionDto result = new RepositoryPermissionDto();
|
RepositoryPermissionDto result = new RepositoryPermissionDto();
|
||||||
result.setName(permission.getName());
|
result.setName(permission.getName());
|
||||||
result.setGroupPermission(permission.isGroupPermission());
|
result.setGroupPermission(permission.isGroupPermission());
|
||||||
result.setType(permission.getType().name());
|
result.setVerbs(permission.getVerbs());
|
||||||
String permissionName = Optional.of(permission.getName())
|
String permissionName = Optional.of(permission.getName())
|
||||||
.filter(p -> !permission.isGroupPermission())
|
.filter(p -> !permission.isGroupPermission())
|
||||||
.orElse(GROUP_PREFIX + permission.getName());
|
.orElse(GROUP_PREFIX + permission.getName());
|
||||||
@@ -425,7 +430,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
HttpRequest request = MockHttpRequest
|
HttpRequest request = MockHttpRequest
|
||||||
.create(entry.method, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + entry.path)
|
.create(entry.method, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + entry.path)
|
||||||
.content(entry.content)
|
.content(entry.content)
|
||||||
.contentType(VndMediaType.PERMISSION);
|
.contentType(VndMediaType.REPOSITORY_PERMISSION);
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
log.info("Test the Request :{}", entry);
|
log.info("Test the Request :{}", entry);
|
||||||
assertThat(response.getStatus())
|
assertThat(response.getStatus())
|
||||||
@@ -8,11 +8,11 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
@@ -36,7 +36,7 @@ public class RepositoryPermissionToRepositoryPermissionDtoMapperTest {
|
|||||||
@SubjectAware(username = "trillian", password = "secret")
|
@SubjectAware(username = "trillian", password = "secret")
|
||||||
public void shouldMapGroupPermissionCorrectly() {
|
public void shouldMapGroupPermissionCorrectly() {
|
||||||
Repository repository = getDummyRepository();
|
Repository repository = getDummyRepository();
|
||||||
RepositoryPermission permission = new RepositoryPermission("42", PermissionType.OWNER, true);
|
RepositoryPermission permission = new RepositoryPermission("42", asList("read","modify","delete"), true);
|
||||||
|
|
||||||
RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository);
|
RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository);
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public class RepositoryPermissionToRepositoryPermissionDtoMapperTest {
|
|||||||
@SubjectAware(username = "trillian", password = "secret")
|
@SubjectAware(username = "trillian", password = "secret")
|
||||||
public void shouldMapNonGroupPermissionCorrectly() {
|
public void shouldMapNonGroupPermissionCorrectly() {
|
||||||
Repository repository = getDummyRepository();
|
Repository repository = getDummyRepository();
|
||||||
RepositoryPermission permission = new RepositoryPermission("42", PermissionType.OWNER, false);
|
RepositoryPermission permission = new RepositoryPermission("42", asList("read","modify","delete"), false);
|
||||||
|
|
||||||
RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository);
|
RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.google.common.io.Resources;
|
|||||||
import com.google.inject.util.Providers;
|
import com.google.inject.util.Providers;
|
||||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.assertj.core.api.Assertions;
|
|
||||||
import org.jboss.resteasy.core.Dispatcher;
|
import org.jboss.resteasy.core.Dispatcher;
|
||||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||||
@@ -18,8 +17,6 @@ import org.mockito.InjectMocks;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryIsNotArchivedException;
|
import sonia.scm.repository.RepositoryIsNotArchivedException;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
@@ -41,15 +38,12 @@ 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_NO_CONTENT;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentCaptor.forClass;
|
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Matchers.anyObject;
|
import static org.mockito.Matchers.anyObject;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.doNothing;
|
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -291,36 +285,14 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
Assertions.assertThat(createCaptor.getValue().getPermissions())
|
assertThat(createCaptor.getValue().getPermissions())
|
||||||
.hasSize(1)
|
.hasSize(1)
|
||||||
.allSatisfy(p -> {
|
.allSatisfy(p -> {
|
||||||
assertThat(p.getName()).isEqualTo("trillian");
|
assertThat(p.getName()).isEqualTo("trillian");
|
||||||
assertThat(p.getType()).isEqualTo(PermissionType.OWNER);
|
assertThat(p.getVerbs()).containsExactly("*");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@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<Repository> 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldCreateArrayOfProtocolUrls() throws Exception {
|
public void shouldCreateArrayOfProtocolUrls() throws Exception {
|
||||||
mockRepository("space", "repo");
|
mockRepository("space", "repo");
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public abstract class RepositoryTestBase {
|
|||||||
protected Provider<ChangesetRootResource> changesetRootResource;
|
protected Provider<ChangesetRootResource> changesetRootResource;
|
||||||
protected Provider<SourceRootResource> sourceRootResource;
|
protected Provider<SourceRootResource> sourceRootResource;
|
||||||
protected Provider<ContentResource> contentResource;
|
protected Provider<ContentResource> contentResource;
|
||||||
protected Provider<PermissionRootResource> permissionRootResource;
|
protected Provider<RepositoryPermissionRootResource> permissionRootResource;
|
||||||
protected Provider<DiffRootResource> diffRootResource;
|
protected Provider<DiffRootResource> diffRootResource;
|
||||||
protected Provider<ModificationsRootResource> modificationsRootResource;
|
protected Provider<ModificationsRootResource> modificationsRootResource;
|
||||||
protected Provider<FileHistoryRootResource> fileHistoryRootResource;
|
protected Provider<FileHistoryRootResource> fileHistoryRootResource;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import org.junit.Test;
|
|||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import sonia.scm.repository.HealthCheckFailure;
|
import sonia.scm.repository.HealthCheckFailure;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.api.Command;
|
import sonia.scm.repository.api.Command;
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
@@ -238,7 +236,6 @@ public class RepositoryToRepositoryDtoMapperTest {
|
|||||||
repository.setId("1");
|
repository.setId("1");
|
||||||
repository.setCreationDate(System.currentTimeMillis());
|
repository.setCreationDate(System.currentTimeMillis());
|
||||||
repository.setHealthCheckFailures(singletonList(new HealthCheckFailure("1", "summary", "url", "failure")));
|
repository.setHealthCheckFailures(singletonList(new HealthCheckFailure("1", "summary", "url", "failure")));
|
||||||
repository.setPermissions(singletonList(new RepositoryPermission("permission", PermissionType.READ)));
|
|
||||||
|
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class ResourceLinksMock {
|
|||||||
when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(uriInfo));
|
when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(uriInfo));
|
||||||
when(resourceLinks.merge()).thenReturn(new ResourceLinks.MergeLinks(uriInfo));
|
when(resourceLinks.merge()).thenReturn(new ResourceLinks.MergeLinks(uriInfo));
|
||||||
when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(uriInfo));
|
when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(uriInfo));
|
||||||
|
when(resourceLinks.availableRepositoryPermissions()).thenReturn(new ResourceLinks.AvailableRepositoryPermissionLinks(uriInfo));
|
||||||
|
|
||||||
return resourceLinks;
|
return resourceLinks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import sonia.scm.api.rest.ObjectMapperProvider;
|
|||||||
import sonia.scm.api.v2.resources.RepositoryDto;
|
import sonia.scm.api.v2.resources.RepositoryDto;
|
||||||
import sonia.scm.api.v2.resources.UserDto;
|
import sonia.scm.api.v2.resources.UserDto;
|
||||||
import sonia.scm.api.v2.resources.UserToUserDtoMapperImpl;
|
import sonia.scm.api.v2.resources.UserToUserDtoMapperImpl;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserTestData;
|
import sonia.scm.user.UserTestData;
|
||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
@@ -117,10 +116,6 @@ public class GitLfsITCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLfsAPIWithOwnerPermissions() throws IOException {
|
public void testLfsAPIWithOwnerPermissions() throws IOException {
|
||||||
uploadAndDownloadAsUser(PermissionType.OWNER);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void uploadAndDownloadAsUser(PermissionType permissionType) throws IOException {
|
|
||||||
User trillian = UserTestData.createTrillian();
|
User trillian = UserTestData.createTrillian();
|
||||||
trillian.setPassword("secret123");
|
trillian.setPassword("secret123");
|
||||||
createUser(trillian);
|
createUser(trillian);
|
||||||
@@ -129,8 +124,8 @@ public class GitLfsITCase {
|
|||||||
String permissionsUrl = repository.getLinks().getLinkBy("permissions").get().getHref();
|
String permissionsUrl = repository.getLinks().getLinkBy("permissions").get().getHref();
|
||||||
IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl))
|
IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl))
|
||||||
.accept("*/*")
|
.accept("*/*")
|
||||||
.type(VndMediaType.PERMISSION)
|
.type(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
.post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"type\":\"WRITE\"}");
|
.post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"verbs\":[\"*\"]}");
|
||||||
|
|
||||||
ScmClient client = new ScmClient(trillian.getId(), "secret123");
|
ScmClient client = new ScmClient(trillian.getId(), "secret123");
|
||||||
|
|
||||||
@@ -140,11 +135,6 @@ public class GitLfsITCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLfsAPIWithWritePermissions() throws IOException {
|
|
||||||
uploadAndDownloadAsUser(PermissionType.WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createUser(User user) {
|
private void createUser(User user) {
|
||||||
UserDto dto = new UserToUserDtoMapperImpl(){
|
UserDto dto = new UserToUserDtoMapperImpl(){
|
||||||
@Override
|
@Override
|
||||||
@@ -175,8 +165,8 @@ public class GitLfsITCase {
|
|||||||
String permissionsUrl = repository.getLinks().getLinkBy("permissions").get().getHref();
|
String permissionsUrl = repository.getLinks().getLinkBy("permissions").get().getHref();
|
||||||
IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl))
|
IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl))
|
||||||
.accept("*/*")
|
.accept("*/*")
|
||||||
.type(VndMediaType.PERMISSION)
|
.type(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
.post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"type\":\"READ\"}");
|
.post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"verbs\":[\"read\"]}");
|
||||||
|
|
||||||
ScmClient client = new ScmClient(trillian.getId(), "secret123");
|
ScmClient client = new ScmClient(trillian.getId(), "secret123");
|
||||||
uploadAndDownload(client);
|
uploadAndDownload(client);
|
||||||
@@ -196,8 +186,8 @@ public class GitLfsITCase {
|
|||||||
String permissionsUrl = repository.getLinks().getLinkBy("permissions").get().getHref();
|
String permissionsUrl = repository.getLinks().getLinkBy("permissions").get().getHref();
|
||||||
IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl))
|
IntegrationTestUtil.createResource(adminClient, URI.create(permissionsUrl))
|
||||||
.accept("*/*")
|
.accept("*/*")
|
||||||
.type(VndMediaType.PERMISSION)
|
.type(VndMediaType.REPOSITORY_PERMISSION)
|
||||||
.post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"type\":\"READ\"}");
|
.post(ClientResponse.class, "{\"name\": \""+ trillian.getId() +"\", \"verbs\":[\"read\",\"pull\"]}");
|
||||||
|
|
||||||
// upload data as admin
|
// upload data as admin
|
||||||
String data = UUID.randomUUID().toString();
|
String data = UUID.randomUUID().toString();
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ public class DefaultRepositoryManagerPerfTest {
|
|||||||
public void setUpObjectUnderTest(){
|
public void setUpObjectUnderTest(){
|
||||||
when(repositoryHandler.getType()).thenReturn(new RepositoryType(REPOSITORY_TYPE, REPOSITORY_TYPE, Sets.newHashSet()));
|
when(repositoryHandler.getType()).thenReturn(new RepositoryType(REPOSITORY_TYPE, REPOSITORY_TYPE, Sets.newHashSet()));
|
||||||
Set<RepositoryHandler> handlerSet = ImmutableSet.of(repositoryHandler);
|
Set<RepositoryHandler> handlerSet = ImmutableSet.of(repositoryHandler);
|
||||||
RepositoryMatcher repositoryMatcher = new RepositoryMatcher(Collections.<RepositoryPathMatcher>emptySet());
|
|
||||||
NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
|
NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
|
||||||
repositoryManager = new DefaultRepositoryManager(
|
repositoryManager = new DefaultRepositoryManager(
|
||||||
configuration,
|
configuration,
|
||||||
@@ -138,7 +137,7 @@ public class DefaultRepositoryManagerPerfTest {
|
|||||||
/**
|
/**
|
||||||
* Start performance test and ensure that the timeout is not reached.
|
* Start performance test and ensure that the timeout is not reached.
|
||||||
*/
|
*/
|
||||||
@Test(timeout = 6000l)
|
@Test(timeout = 6000L)
|
||||||
public void perfTestGetAll(){
|
public void perfTestGetAll(){
|
||||||
SecurityUtils.getSubject().login(new UsernamePasswordToken("trillian", "secret"));
|
SecurityUtils.getSubject().login(new UsernamePasswordToken("trillian", "secret"));
|
||||||
|
|
||||||
@@ -155,7 +154,7 @@ public class DefaultRepositoryManagerPerfTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long calculateAverage(List<Long> times) {
|
private long calculateAverage(List<Long> times) {
|
||||||
Long sum = 0l;
|
Long sum = 0L;
|
||||||
if(!times.isEmpty()) {
|
if(!times.isEmpty()) {
|
||||||
for (Long time : times) {
|
for (Long time : times) {
|
||||||
sum += time;
|
sum += time;
|
||||||
@@ -183,9 +182,8 @@ private long calculateAverage(List<Long> times) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Repository createTestRepository(int number) {
|
private Repository createTestRepository(int number) {
|
||||||
Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number);
|
return new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number);
|
||||||
repository.addPermission(new RepositoryPermission("trillian", PermissionType.READ));
|
|
||||||
return repository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class DummyRealm extends AuthorizingRealm {
|
static class DummyRealm extends AuthorizingRealm {
|
||||||
|
|||||||
@@ -32,14 +32,12 @@
|
|||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import org.junit.Test;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
import sonia.scm.HandlerEventType;
|
import sonia.scm.HandlerEventType;
|
||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
import sonia.scm.group.GroupEvent;
|
import sonia.scm.group.GroupEvent;
|
||||||
import sonia.scm.group.GroupModificationEvent;
|
import sonia.scm.group.GroupModificationEvent;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryEvent;
|
import sonia.scm.repository.RepositoryEvent;
|
||||||
import sonia.scm.repository.RepositoryModificationEvent;
|
import sonia.scm.repository.RepositoryModificationEvent;
|
||||||
@@ -50,6 +48,14 @@ import sonia.scm.user.UserEvent;
|
|||||||
import sonia.scm.user.UserModificationEvent;
|
import sonia.scm.user.UserModificationEvent;
|
||||||
import sonia.scm.user.UserTestData;
|
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}.
|
* Unit tests for {@link AuthorizationChangedEventProducer}.
|
||||||
*
|
*
|
||||||
@@ -87,7 +93,12 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
assertTrue(producer.event.isEveryUserAffected());
|
assertTrue(producer.event.isEveryUserAffected());
|
||||||
assertEquals(username, producer.event.getNameOfAffectedUser());
|
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.
|
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.user.UserEvent)} with modified user.
|
||||||
*/
|
*/
|
||||||
@@ -127,11 +138,6 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
assertGlobalEventIsFired();
|
assertGlobalEventIsFired();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertGlobalEventIsFired(){
|
|
||||||
assertNotNull(producer.event);
|
|
||||||
assertFalse(producer.event.isEveryUserAffected());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.group.GroupEvent)} with modified groups.
|
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.group.GroupEvent)} with modified groups.
|
||||||
*/
|
*/
|
||||||
@@ -174,40 +180,49 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
{
|
{
|
||||||
Repository repositoryModified = RepositoryTestData.createHeartOfGold();
|
Repository repositoryModified = RepositoryTestData.createHeartOfGold();
|
||||||
repositoryModified.setName("test123");
|
repositoryModified.setName("test123");
|
||||||
repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test")));
|
repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test", singletonList("read"), false)));
|
||||||
|
|
||||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
Repository repository = RepositoryTestData.createHeartOfGold();
|
||||||
repository.setPermissions(Lists.newArrayList(new RepositoryPermission("test")));
|
repository.setPermissions(Lists.newArrayList(new RepositoryPermission("test", singletonList("read"), false)));
|
||||||
|
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.BEFORE_CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.BEFORE_CREATE, repositoryModified, repository));
|
||||||
assertEventIsNotFired();
|
assertEventIsNotFired();
|
||||||
|
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
||||||
assertEventIsNotFired();
|
assertEventIsNotFired();
|
||||||
|
|
||||||
repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test")));
|
repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test", singletonList("read"), false)));
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
||||||
assertEventIsNotFired();
|
assertEventIsNotFired();
|
||||||
|
|
||||||
repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test123")));
|
repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test123", singletonList("read"), false)));
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
||||||
assertGlobalEventIsFired();
|
assertGlobalEventIsFired();
|
||||||
|
|
||||||
resetStoredEvent();
|
resetStoredEvent();
|
||||||
|
|
||||||
repositoryModified.setPermissions(
|
repositoryModified.setPermissions(
|
||||||
Lists.newArrayList(new RepositoryPermission("test", PermissionType.READ, true))
|
Lists.newArrayList(new RepositoryPermission("test", singletonList("read"), true))
|
||||||
);
|
);
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
||||||
assertGlobalEventIsFired();
|
assertGlobalEventIsFired();
|
||||||
|
|
||||||
resetStoredEvent();
|
resetStoredEvent();
|
||||||
|
|
||||||
repositoryModified.setPermissions(
|
repositoryModified.setPermissions(
|
||||||
Lists.newArrayList(new RepositoryPermission("test", PermissionType.WRITE))
|
Lists.newArrayList(new RepositoryPermission("test", asList("read", "write"), false))
|
||||||
);
|
);
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
||||||
assertGlobalEventIsFired();
|
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(){
|
private void resetStoredEvent(){
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ import sonia.scm.cache.Cache;
|
|||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.group.GroupNames;
|
import sonia.scm.group.GroupNames;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryDAO;
|
import sonia.scm.repository.RepositoryDAO;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
@@ -59,6 +58,7 @@ import sonia.scm.repository.RepositoryTestData;
|
|||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserTestData;
|
import sonia.scm.user.UserTestData;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
@@ -225,10 +225,10 @@ public class DefaultAuthorizationCollectorTest {
|
|||||||
authenticate(UserTestData.createTrillian(), group);
|
authenticate(UserTestData.createTrillian(), group);
|
||||||
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
|
||||||
heartOfGold.setId("one");
|
heartOfGold.setId("one");
|
||||||
heartOfGold.setPermissions(Lists.newArrayList(new RepositoryPermission("trillian")));
|
heartOfGold.setPermissions(Lists.newArrayList(new RepositoryPermission("trillian", asList("read", "pull"), false)));
|
||||||
Repository puzzle42 = RepositoryTestData.create42Puzzle();
|
Repository puzzle42 = RepositoryTestData.create42Puzzle();
|
||||||
puzzle42.setId("two");
|
puzzle42.setId("two");
|
||||||
RepositoryPermission permission = new RepositoryPermission(group, PermissionType.WRITE, true);
|
RepositoryPermission permission = new RepositoryPermission(group, asList("read", "pull", "push"), true);
|
||||||
puzzle42.setPermissions(Lists.newArrayList(permission));
|
puzzle42.setPermissions(Lists.newArrayList(permission));
|
||||||
when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42));
|
when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
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.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));
|
||||||
|
repositoryPermissionProvider = new RepositoryPermissionProvider(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::containsOnlyAvailableVerbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void containsOnlyAvailableVerbs(RepositoryRole role) {
|
||||||
|
assertThat(role.getVerbs()).isSubsetOf(repositoryPermissionProvider.availableVerbs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user