mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
merged master
This commit is contained in:
5
Jenkinsfile
vendored
5
Jenkinsfile
vendored
@@ -50,6 +50,11 @@ node('docker') {
|
|||||||
def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}"
|
def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}"
|
||||||
|
|
||||||
if (isMainBranch()) {
|
if (isMainBranch()) {
|
||||||
|
stage('Archive') {
|
||||||
|
archiveArtifacts 'scm-webapp/target/scm-webapp.war'
|
||||||
|
archiveArtifacts 'scm-server/target/scm-server-app.*'
|
||||||
|
}
|
||||||
|
|
||||||
stage('Docker') {
|
stage('Docker') {
|
||||||
def image = docker.build('cloudogu/scm-manager')
|
def image = docker.build('cloudogu/scm-manager')
|
||||||
docker.withRegistry('', 'hub.docker.com-cesmarvin') {
|
docker.withRegistry('', 'hub.docker.com-cesmarvin') {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public final class ScmState
|
|||||||
public ScmState(String version, User user, Collection<String> groups,
|
public ScmState(String version, User user, Collection<String> groups,
|
||||||
String token, Collection<RepositoryType> repositoryTypes, String defaultUserType,
|
String token, Collection<RepositoryType> repositoryTypes, String defaultUserType,
|
||||||
ScmClientConfig clientConfig, List<String> assignedPermission,
|
ScmClientConfig clientConfig, List<String> assignedPermission,
|
||||||
List<PermissionDescriptor> availablePermissions)
|
Collection<PermissionDescriptor> availablePermissions)
|
||||||
{
|
{
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
@@ -119,7 +119,7 @@ public final class ScmState
|
|||||||
* @return available global permissions
|
* @return available global permissions
|
||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
public List<PermissionDescriptor> getAvailablePermissions()
|
public Collection<PermissionDescriptor> getAvailablePermissions()
|
||||||
{
|
{
|
||||||
return availablePermissions;
|
return availablePermissions;
|
||||||
}
|
}
|
||||||
@@ -232,7 +232,7 @@ public final class ScmState
|
|||||||
* Avaliable global permission
|
* Avaliable global permission
|
||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
private List<PermissionDescriptor> availablePermissions;
|
private Collection<PermissionDescriptor> availablePermissions;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private ScmClientConfig clientConfig;
|
private ScmClientConfig clientConfig;
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ public final class ScmStateFactory
|
|||||||
User user = collection.oneByType(User.class);
|
User user = collection.oneByType(User.class);
|
||||||
GroupNames groups = collection.oneByType(GroupNames.class);
|
GroupNames groups = collection.oneByType(GroupNames.class);
|
||||||
|
|
||||||
List<PermissionDescriptor> ap = Collections.EMPTY_LIST;
|
Collection<PermissionDescriptor> ap = Collections.EMPTY_LIST;
|
||||||
|
|
||||||
if (subject.hasRole(Role.ADMIN))
|
if (subject.hasRole(Role.ADMIN))
|
||||||
{
|
{
|
||||||
@@ -150,7 +150,7 @@ public final class ScmStateFactory
|
|||||||
|
|
||||||
private ScmState createState(User user, Collection<String> groups,
|
private ScmState createState(User user, Collection<String> groups,
|
||||||
String token, List<String> assignedPermissions,
|
String token, List<String> assignedPermissions,
|
||||||
List<PermissionDescriptor> availablePermissions)
|
Collection<PermissionDescriptor> availablePermissions)
|
||||||
{
|
{
|
||||||
User u = user.clone();
|
User u = user.clone();
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,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;
|
||||||
private final Set<Permission> permissions = new HashSet<>();
|
private final Set<RepositoryPermission> permissions = new HashSet<>();
|
||||||
@XmlElement(name = "public")
|
@XmlElement(name = "public")
|
||||||
private boolean publicReadable = false;
|
private boolean publicReadable = false;
|
||||||
private boolean archived = false;
|
private boolean archived = false;
|
||||||
@@ -122,7 +122,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
* @param permissions permissions for specific users and groups.
|
* @param permissions permissions for specific users and groups.
|
||||||
*/
|
*/
|
||||||
public Repository(String id, String type, String namespace, String name, String contact,
|
public Repository(String id, String type, String namespace, String name, String contact,
|
||||||
String description, Permission... permissions) {
|
String description, RepositoryPermission... permissions) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.namespace = namespace;
|
this.namespace = namespace;
|
||||||
@@ -201,7 +201,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
return new NamespaceAndName(getNamespace(), getName());
|
return new NamespaceAndName(getNamespace(), getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Permission> getPermissions() {
|
public Collection<RepositoryPermission> getPermissions() {
|
||||||
return Collections.unmodifiableCollection(permissions);
|
return Collections.unmodifiableCollection(permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,16 +297,16 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPermissions(Collection<Permission> permissions) {
|
public void setPermissions(Collection<RepositoryPermission> permissions) {
|
||||||
this.permissions.clear();
|
this.permissions.clear();
|
||||||
this.permissions.addAll(permissions);
|
this.permissions.addAll(permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPermission(Permission newPermission) {
|
public void addPermission(RepositoryPermission newPermission) {
|
||||||
this.permissions.add(newPermission);
|
this.permissions.add(newPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePermission(Permission permission) {
|
public void removePermission(RepositoryPermission permission) {
|
||||||
this.permissions.remove(permission);
|
this.permissions.remove(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "permissions")
|
@XmlRootElement(name = "permissions")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class Permission implements PermissionObject, Serializable
|
public class RepositoryPermission implements PermissionObject, Serializable
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final long serialVersionUID = -2915175031430884040L;
|
private static final long serialVersionUID = -2915175031430884040L;
|
||||||
@@ -63,41 +63,41 @@ public class Permission implements PermissionObject, Serializable
|
|||||||
private PermissionType type = PermissionType.READ;
|
private PermissionType type = PermissionType.READ;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new {@link Permission}.
|
* Constructs a new {@link RepositoryPermission}.
|
||||||
* This constructor is used by JAXB.
|
* This constructor is used by JAXB.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public Permission() {}
|
public RepositoryPermission() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new {@link Permission} with type = {@link PermissionType#READ}
|
* Constructs a new {@link RepositoryPermission} with type = {@link PermissionType#READ}
|
||||||
* for the specified user.
|
* for the specified user.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param name name of the user
|
* @param name name of the user
|
||||||
*/
|
*/
|
||||||
public Permission(String name)
|
public RepositoryPermission(String name)
|
||||||
{
|
{
|
||||||
this();
|
this();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new {@link Permission} with the specified type for
|
* Constructs a new {@link RepositoryPermission} with the specified type for
|
||||||
* the given user.
|
* the given user.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param name name of the user
|
* @param name name of the user
|
||||||
* @param type type of the permission
|
* @param type type of the permission
|
||||||
*/
|
*/
|
||||||
public Permission(String name, PermissionType type)
|
public RepositoryPermission(String name, PermissionType type)
|
||||||
{
|
{
|
||||||
this(name);
|
this(name);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new {@link Permission} with the specified type for
|
* Constructs a new {@link RepositoryPermission} with the specified type for
|
||||||
* the given user or group.
|
* the given user or group.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
@@ -105,7 +105,7 @@ public class Permission implements PermissionObject, Serializable
|
|||||||
* @param type type of the permission
|
* @param type type of the permission
|
||||||
* @param groupPermission true if the permission is a permission for a group
|
* @param groupPermission true if the permission is a permission for a group
|
||||||
*/
|
*/
|
||||||
public Permission(String name, PermissionType type, boolean groupPermission)
|
public RepositoryPermission(String name, PermissionType type, boolean groupPermission)
|
||||||
{
|
{
|
||||||
this(name, type);
|
this(name, type);
|
||||||
this.groupPermission = groupPermission;
|
this.groupPermission = groupPermission;
|
||||||
@@ -114,12 +114,12 @@ public class Permission implements PermissionObject, Serializable
|
|||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the {@link Permission} is the same as the obj argument.
|
* Returns true if the {@link RepositoryPermission} is the same as the obj argument.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param obj the reference object with which to compare
|
* @param obj the reference object with which to compare
|
||||||
*
|
*
|
||||||
* @return true if the {@link Permission} is the same as the obj argument
|
* @return true if the {@link RepositoryPermission} is the same as the obj argument
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj)
|
public boolean equals(Object obj)
|
||||||
@@ -134,7 +134,7 @@ public class Permission implements PermissionObject, Serializable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Permission other = (Permission) obj;
|
final RepositoryPermission other = (RepositoryPermission) obj;
|
||||||
|
|
||||||
return Objects.equal(name, other.name)
|
return Objects.equal(name, other.name)
|
||||||
&& Objects.equal(type, other.type)
|
&& Objects.equal(type, other.type)
|
||||||
@@ -142,10 +142,10 @@ public class Permission implements PermissionObject, Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hash code value for the {@link Permission}.
|
* Returns the hash code value for the {@link RepositoryPermission}.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @return the hash code value for the {@link Permission}
|
* @return the hash code value for the {@link RepositoryPermission}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
@@ -33,6 +33,7 @@ package sonia.scm.security;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An access token can be used to access scm-manager without providing username and password. An {@link AccessToken} can
|
* An access token can be used to access scm-manager without providing username and password. An {@link AccessToken} can
|
||||||
@@ -103,6 +104,13 @@ public interface AccessToken {
|
|||||||
*/
|
*/
|
||||||
Scope getScope();
|
Scope getScope();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns name of groups, in which the user should be a member.
|
||||||
|
*
|
||||||
|
* @return name of groups
|
||||||
|
*/
|
||||||
|
Set<String> getGroups();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an optional value of a custom token field.
|
* Returns an optional value of a custom token field.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -89,16 +89,25 @@ public interface AccessTokenBuilder {
|
|||||||
* @return {@code this}
|
* @return {@code this}
|
||||||
*/
|
*/
|
||||||
AccessTokenBuilder refreshableFor(long count, TimeUnit unit);
|
AccessTokenBuilder refreshableFor(long count, TimeUnit unit);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces the permissions of the token by providing a scope.
|
* Reduces the permissions of the token by providing a scope.
|
||||||
*
|
*
|
||||||
* @param scope scope of token
|
* @param scope scope of token
|
||||||
*
|
*
|
||||||
* @return {@code this}
|
* @return {@code this}
|
||||||
*/
|
*/
|
||||||
AccessTokenBuilder scope(Scope scope);
|
AccessTokenBuilder scope(Scope scope);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the logged in user as member of the given groups.
|
||||||
|
*
|
||||||
|
* @param groups group names
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
AccessTokenBuilder groups(String... groups);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link AccessToken} with the provided settings.
|
* Creates a new {@link AccessToken} with the provided settings.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -89,8 +89,12 @@ public class AssignedPermission implements PermissionObject, Serializable
|
|||||||
*/
|
*/
|
||||||
public AssignedPermission(String name, String permission)
|
public AssignedPermission(String name, String permission)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this(name, new PermissionDescriptor(permission));
|
||||||
this.permission = permission;
|
}
|
||||||
|
|
||||||
|
public AssignedPermission(String name, PermissionDescriptor permission)
|
||||||
|
{
|
||||||
|
this(name, false, permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,6 +107,12 @@ public class AssignedPermission implements PermissionObject, Serializable
|
|||||||
*/
|
*/
|
||||||
public AssignedPermission(String name, boolean groupPermission,
|
public AssignedPermission(String name, boolean groupPermission,
|
||||||
String permission)
|
String permission)
|
||||||
|
{
|
||||||
|
this(name, groupPermission, new PermissionDescriptor(permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssignedPermission(String name, boolean groupPermission,
|
||||||
|
PermissionDescriptor permission)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.groupPermission = groupPermission;
|
this.groupPermission = groupPermission;
|
||||||
@@ -173,12 +183,9 @@ public class AssignedPermission implements PermissionObject, Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the string representation of the permission.
|
* Returns the description of the permission.
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return string representation of the permission
|
|
||||||
*/
|
*/
|
||||||
public String getPermission()
|
public PermissionDescriptor getPermission()
|
||||||
{
|
{
|
||||||
return permission;
|
return permission;
|
||||||
}
|
}
|
||||||
@@ -205,5 +212,5 @@ public class AssignedPermission implements PermissionObject, Serializable
|
|||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/** string representation of the permission */
|
/** string representation of the permission */
|
||||||
private String permission;
|
private PermissionDescriptor permission;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import java.io.Serializable;
|
|||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
@Event
|
@Event
|
||||||
public final class StoredAssignedPermissionEvent implements Serializable
|
public final class AssignedPermissionEvent implements Serializable
|
||||||
{
|
{
|
||||||
|
|
||||||
/** serial version uid */
|
/** serial version uid */
|
||||||
@@ -60,14 +60,14 @@ public final class StoredAssignedPermissionEvent implements Serializable
|
|||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new StoredAssignedPermissionEvent.
|
* Constructs a new AssignedPermissionEvent.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param type type of the event
|
* @param type type of the event
|
||||||
* @param permission permission object which has changed
|
* @param permission permission object which has changed
|
||||||
*/
|
*/
|
||||||
public StoredAssignedPermissionEvent(HandlerEventType type,
|
public AssignedPermissionEvent(HandlerEventType type,
|
||||||
StoredAssignedPermission permission)
|
AssignedPermission permission)
|
||||||
{
|
{
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.permission = permission;
|
this.permission = permission;
|
||||||
@@ -91,8 +91,8 @@ public final class StoredAssignedPermissionEvent implements Serializable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final StoredAssignedPermissionEvent other =
|
final AssignedPermissionEvent other =
|
||||||
(StoredAssignedPermissionEvent) obj;
|
(AssignedPermissionEvent) obj;
|
||||||
|
|
||||||
return Objects.equal(type, other.type)
|
return Objects.equal(type, other.type)
|
||||||
&& Objects.equal(permission, other.permission);
|
&& Objects.equal(permission, other.permission);
|
||||||
@@ -140,7 +140,7 @@ public final class StoredAssignedPermissionEvent implements Serializable
|
|||||||
*
|
*
|
||||||
* @return changed permission
|
* @return changed permission
|
||||||
*/
|
*/
|
||||||
public StoredAssignedPermission getPermission()
|
public AssignedPermission getPermission()
|
||||||
{
|
{
|
||||||
return permission;
|
return permission;
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ public final class StoredAssignedPermissionEvent implements Serializable
|
|||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** changed permission */
|
/** changed permission */
|
||||||
private StoredAssignedPermission permission;
|
private AssignedPermission permission;
|
||||||
|
|
||||||
/** type of the event */
|
/** type of the event */
|
||||||
private HandlerEventType type;
|
private HandlerEventType type;
|
||||||
@@ -37,7 +37,6 @@ import com.google.common.base.MoreObjects;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.ImmutableSet.Builder;
|
import com.google.common.collect.ImmutableSet.Builder;
|
||||||
import org.apache.shiro.authc.AuthenticationException;
|
|
||||||
import org.apache.shiro.authc.AuthenticationInfo;
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
import org.apache.shiro.authc.AuthenticationToken;
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
import org.apache.shiro.authc.DisabledAccountException;
|
import org.apache.shiro.authc.DisabledAccountException;
|
||||||
@@ -54,6 +53,8 @@ import sonia.scm.group.GroupNames;
|
|||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserDAO;
|
import sonia.scm.user.UserDAO;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,8 +64,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public final class DAORealmHelper
|
public final class DAORealmHelper {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the logger for DAORealmHelper
|
* the logger for DAORealmHelper
|
||||||
@@ -109,37 +109,37 @@ public final class DAORealmHelper
|
|||||||
public CredentialsMatcher wrapCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
|
public CredentialsMatcher wrapCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
|
||||||
return new RetryLimitPasswordMatcher(loginAttemptHandler, credentialsMatcher);
|
return new RetryLimitPasswordMatcher(loginAttemptHandler, credentialsMatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Creates {@link AuthenticationInfo} from a {@link UsernamePasswordToken}. The method accepts
|
||||||
|
* {@link AuthenticationInfo} as argument, so that the caller does not need to cast.
|
||||||
*
|
*
|
||||||
|
* @param token authentication token, it must be {@link UsernamePasswordToken}
|
||||||
*
|
*
|
||||||
* @param token
|
* @return authentication info
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws AuthenticationException
|
|
||||||
*/
|
*/
|
||||||
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
|
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) {
|
||||||
checkArgument(token instanceof UsernamePasswordToken, "%s is required", UsernamePasswordToken.class);
|
checkArgument(token instanceof UsernamePasswordToken, "%s is required", UsernamePasswordToken.class);
|
||||||
|
|
||||||
UsernamePasswordToken upt = (UsernamePasswordToken) token;
|
UsernamePasswordToken upt = (UsernamePasswordToken) token;
|
||||||
String principal = upt.getUsername();
|
String principal = upt.getUsername();
|
||||||
|
|
||||||
return getAuthenticationInfo(principal, null, null);
|
return getAuthenticationInfo(principal, null, null, Collections.emptySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Returns a builder for {@link AuthenticationInfo}.
|
||||||
*
|
*
|
||||||
|
* @param principal name of principal (username)
|
||||||
*
|
*
|
||||||
* @param principal
|
* @return authentication info builder
|
||||||
* @param credentials
|
|
||||||
* @param scope
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
public AuthenticationInfo getAuthenticationInfo(String principal, String credentials, Scope scope) {
|
public AuthenticationInfoBuilder authenticationInfoBuilder(String principal) {
|
||||||
|
return new AuthenticationInfoBuilder(principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private AuthenticationInfo getAuthenticationInfo(String principal, String credentials, Scope scope, Iterable<String> groups) {
|
||||||
checkArgument(!Strings.isNullOrEmpty(principal), "username is required");
|
checkArgument(!Strings.isNullOrEmpty(principal), "username is required");
|
||||||
|
|
||||||
LOG.debug("try to authenticate {}", principal);
|
LOG.debug("try to authenticate {}", principal);
|
||||||
@@ -157,7 +157,7 @@ public final class DAORealmHelper
|
|||||||
|
|
||||||
collection.add(principal, realm);
|
collection.add(principal, realm);
|
||||||
collection.add(user, realm);
|
collection.add(user, realm);
|
||||||
collection.add(collectGroups(principal), realm);
|
collection.add(collectGroups(principal, groups), realm);
|
||||||
collection.add(MoreObjects.firstNonNull(scope, Scope.empty()), realm);
|
collection.add(MoreObjects.firstNonNull(scope, Scope.empty()), realm);
|
||||||
|
|
||||||
String creds = credentials;
|
String creds = credentials;
|
||||||
@@ -171,11 +171,15 @@ public final class DAORealmHelper
|
|||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
private GroupNames collectGroups(String principal) {
|
private GroupNames collectGroups(String principal, Iterable<String> groupNames) {
|
||||||
Builder<String> builder = ImmutableSet.builder();
|
Builder<String> builder = ImmutableSet.builder();
|
||||||
|
|
||||||
builder.add(GroupNames.AUTHENTICATED);
|
builder.add(GroupNames.AUTHENTICATED);
|
||||||
|
|
||||||
|
for (String group : groupNames) {
|
||||||
|
builder.add(group);
|
||||||
|
}
|
||||||
|
|
||||||
for (Group group : groupDAO.getAll()) {
|
for (Group group : groupDAO.getAll()) {
|
||||||
if (group.isMember(principal)) {
|
if (group.isMember(principal)) {
|
||||||
builder.add(group.getName());
|
builder.add(group.getName());
|
||||||
@@ -187,6 +191,69 @@ public final class DAORealmHelper
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for {@link AuthenticationInfo}.
|
||||||
|
*/
|
||||||
|
public class AuthenticationInfoBuilder {
|
||||||
|
|
||||||
|
private final String principal;
|
||||||
|
|
||||||
|
private String credentials;
|
||||||
|
private Scope scope;
|
||||||
|
private Iterable<String> groups = Collections.emptySet();
|
||||||
|
|
||||||
|
private AuthenticationInfoBuilder(String principal) {
|
||||||
|
this.principal = principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With credentials uses the given credentials for the {@link AuthenticationInfo}, this is particularly important
|
||||||
|
* for caching purposes.
|
||||||
|
*
|
||||||
|
* @param credentials credentials such as password
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfoBuilder withCredentials(String credentials) {
|
||||||
|
this.credentials = credentials;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With the scope object it is possible to limit the access permissions to scm-manager.
|
||||||
|
*
|
||||||
|
* @param scope scope object
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfoBuilder withScope(Scope scope) {
|
||||||
|
this.scope = scope;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With groups adds extra groups, besides those which come from the {@link GroupDAO}, to the authentication info.
|
||||||
|
*
|
||||||
|
* @param groups extra groups
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfoBuilder withGroups(Iterable<String> groups) {
|
||||||
|
this.groups = groups;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build creates the authentication info from the given information.
|
||||||
|
*
|
||||||
|
* @return authentication info
|
||||||
|
*/
|
||||||
|
public AuthenticationInfo build() {
|
||||||
|
return getAuthenticationInfo(principal, credentials, scope, groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static class RetryLimitPasswordMatcher implements CredentialsMatcher {
|
private static class RetryLimitPasswordMatcher implements CredentialsMatcher {
|
||||||
|
|
||||||
private final LoginAttemptHandler loginAttemptHandler;
|
private final LoginAttemptHandler loginAttemptHandler;
|
||||||
|
|||||||
12
scm-core/src/main/java/sonia/scm/security/Permission.java
Normal file
12
scm-core/src/main/java/sonia/scm/security/Permission.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.github.sdorra.ssp.PermissionObject;
|
||||||
|
import com.github.sdorra.ssp.StaticPermissions;
|
||||||
|
|
||||||
|
@StaticPermissions(
|
||||||
|
value = "permission",
|
||||||
|
permissions = {},
|
||||||
|
globalPermissions = {"list", "read", "assign"}
|
||||||
|
)
|
||||||
|
public interface Permission extends PermissionObject {
|
||||||
|
}
|
||||||
@@ -67,19 +67,8 @@ public class PermissionDescriptor implements Serializable
|
|||||||
*/
|
*/
|
||||||
public PermissionDescriptor() {}
|
public PermissionDescriptor() {}
|
||||||
|
|
||||||
/**
|
public PermissionDescriptor(String value)
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param displayName
|
|
||||||
* @param description
|
|
||||||
* @param value
|
|
||||||
*/
|
|
||||||
public PermissionDescriptor(String displayName, String description,
|
|
||||||
String value)
|
|
||||||
{
|
{
|
||||||
this.displayName = displayName;
|
|
||||||
this.description = description;
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +92,7 @@ public class PermissionDescriptor implements Serializable
|
|||||||
|
|
||||||
final PermissionDescriptor other = (PermissionDescriptor) obj;
|
final PermissionDescriptor other = (PermissionDescriptor) obj;
|
||||||
|
|
||||||
return Objects.equal(displayName, other.displayName)
|
return Objects.equal(value, other.value);
|
||||||
&& Objects.equal(description, other.description)
|
|
||||||
&& Objects.equal(value, other.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,7 +101,7 @@ public class PermissionDescriptor implements Serializable
|
|||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
return Objects.hashCode(displayName, description, value);
|
return value.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,8 +113,6 @@ public class PermissionDescriptor implements Serializable
|
|||||||
|
|
||||||
//J-
|
//J-
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
.add("displayName", displayName)
|
|
||||||
.add("description", description)
|
|
||||||
.add("value", value)
|
.add("value", value)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
@@ -136,28 +121,6 @@ public class PermissionDescriptor implements Serializable
|
|||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the description of the permission.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return description
|
|
||||||
*/
|
|
||||||
public String getDescription()
|
|
||||||
{
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the display name of the permission.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return display name
|
|
||||||
*/
|
|
||||||
public String getDisplayName()
|
|
||||||
{
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the string representation of the permission.
|
* Returns the string representation of the permission.
|
||||||
*
|
*
|
||||||
@@ -171,13 +134,6 @@ public class PermissionDescriptor implements Serializable
|
|||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** description */
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
/** display name */
|
|
||||||
@XmlElement(name = "display-name")
|
|
||||||
private String displayName;
|
|
||||||
|
|
||||||
/** value */
|
/** value */
|
||||||
private String value;
|
private String value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,13 +32,8 @@
|
|||||||
|
|
||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
import java.util.Collection;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The SecuritySystem manages global permissions.
|
* The SecuritySystem manages global permissions.
|
||||||
@@ -57,7 +52,7 @@ public interface SecuritySystem
|
|||||||
*
|
*
|
||||||
* @return stored permission
|
* @return stored permission
|
||||||
*/
|
*/
|
||||||
public StoredAssignedPermission addPermission(AssignedPermission permission);
|
void addPermission(AssignedPermission permission);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete stored permission.
|
* Delete stored permission.
|
||||||
@@ -65,51 +60,17 @@ public interface SecuritySystem
|
|||||||
*
|
*
|
||||||
* @param permission permission to be deleted
|
* @param permission permission to be deleted
|
||||||
*/
|
*/
|
||||||
public void deletePermission(StoredAssignedPermission permission);
|
void deletePermission(AssignedPermission permission);
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete stored permission.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param id id of the permission
|
|
||||||
*/
|
|
||||||
public void deletePermission(String id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modify stored permission.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param permission stored permisison
|
|
||||||
*/
|
|
||||||
public void modifyPermission(StoredAssignedPermission permission);
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
|
||||||
* Return all stored permissions.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return stored permission
|
|
||||||
*/
|
|
||||||
public List<StoredAssignedPermission> getAllPermissions();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all available permissions.
|
* Return all available permissions.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @return available permissions
|
* @return available permissions
|
||||||
*/
|
*/
|
||||||
public List<PermissionDescriptor> getAvailablePermissions();
|
Collection<PermissionDescriptor> getAvailablePermissions();
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the stored permission which is stored with the given id.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param id id of the stored permission
|
|
||||||
*
|
|
||||||
* @return stored permission
|
|
||||||
*/
|
|
||||||
public StoredAssignedPermission getPermission(String id);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all stored permissions which are matched by the given
|
* Returns all stored permissions which are matched by the given
|
||||||
@@ -120,6 +81,5 @@ public interface SecuritySystem
|
|||||||
*
|
*
|
||||||
* @return filtered permissions
|
* @return filtered permissions
|
||||||
*/
|
*/
|
||||||
public List<StoredAssignedPermission> getPermissions(
|
Collection<AssignedPermission> getPermissions(Predicate<AssignedPermission> predicate);
|
||||||
Predicate<AssignedPermission> predicate);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ package sonia.scm.security;
|
|||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
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.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class VndMediaType {
|
|||||||
public static final String PASSWORD_CHANGE = PREFIX + "passwordChange" + SUFFIX;
|
public static final String PASSWORD_CHANGE = PREFIX + "passwordChange" + SUFFIX;
|
||||||
@SuppressWarnings("squid:S2068")
|
@SuppressWarnings("squid:S2068")
|
||||||
public static final String PASSWORD_OVERWRITE = PREFIX + "passwordOverwrite" + SUFFIX;
|
public static final String PASSWORD_OVERWRITE = PREFIX + "passwordOverwrite" + SUFFIX;
|
||||||
|
public static final String PERMISSION_COLLECTION = PREFIX + "permissionCollection" + SUFFIX;
|
||||||
public static final String MERGE_RESULT = PREFIX + "mergeResult" + SUFFIX;
|
public static final String MERGE_RESULT = PREFIX + "mergeResult" + SUFFIX;
|
||||||
public static final String MERGE_COMMAND = PREFIX + "mergeCommand" + SUFFIX;
|
public static final String MERGE_COMMAND = PREFIX + "mergeCommand" + SUFFIX;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.DisabledAccountException;
|
||||||
|
import org.apache.shiro.authc.UnknownAccountException;
|
||||||
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.group.Group;
|
||||||
|
import sonia.scm.group.GroupDAO;
|
||||||
|
import sonia.scm.group.GroupNames;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.UserDAO;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class DAORealmHelperTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoginAttemptHandler loginAttemptHandler;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private UserDAO userDAO;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private GroupDAO groupDAO;
|
||||||
|
|
||||||
|
private DAORealmHelper helper;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpObjectUnderTest() {
|
||||||
|
helper = new DAORealmHelper(loginAttemptHandler, userDAO, groupDAO, "hitchhiker");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowExceptionWithoutUsername() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> helper.authenticationInfoBuilder(null).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowExceptionWithEmptyUsername() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> helper.authenticationInfoBuilder("").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowExceptionWithUnknownUser() {
|
||||||
|
assertThrows(UnknownAccountException.class, () -> helper.authenticationInfoBuilder("trillian").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowExceptionOnDisabledAccount() {
|
||||||
|
User user = new User("trillian");
|
||||||
|
user.setActive(false);
|
||||||
|
when(userDAO.get("trillian")).thenReturn(user);
|
||||||
|
|
||||||
|
assertThrows(DisabledAccountException.class, () -> helper.authenticationInfoBuilder("trillian").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnAuthenticationInfo() {
|
||||||
|
User user = new User("trillian");
|
||||||
|
when(userDAO.get("trillian")).thenReturn(user);
|
||||||
|
|
||||||
|
AuthenticationInfo authenticationInfo = helper.authenticationInfoBuilder("trillian").build();
|
||||||
|
PrincipalCollection principals = authenticationInfo.getPrincipals();
|
||||||
|
assertThat(principals.oneByType(User.class)).isSameAs(user);
|
||||||
|
assertThat(principals.oneByType(GroupNames.class)).containsOnly("_authenticated");
|
||||||
|
assertThat(principals.oneByType(Scope.class)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnAuthenticationInfoWithGroups() {
|
||||||
|
User user = new User("trillian");
|
||||||
|
when(userDAO.get("trillian")).thenReturn(user);
|
||||||
|
|
||||||
|
Group one = new Group("xml", "one", "trillian");
|
||||||
|
Group two = new Group("xml", "two", "trillian");
|
||||||
|
Group six = new Group("xml", "six", "dent");
|
||||||
|
when(groupDAO.getAll()).thenReturn(ImmutableList.of(one, two, six));
|
||||||
|
|
||||||
|
AuthenticationInfo authenticationInfo = helper.authenticationInfoBuilder("trillian")
|
||||||
|
.withGroups(ImmutableList.of("three"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PrincipalCollection principals = authenticationInfo.getPrincipals();
|
||||||
|
assertThat(principals.oneByType(GroupNames.class)).containsOnly("_authenticated", "one", "two", "three");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnAuthenticationInfoWithScope() {
|
||||||
|
User user = new User("trillian");
|
||||||
|
when(userDAO.get("trillian")).thenReturn(user);
|
||||||
|
|
||||||
|
Scope scope = Scope.valueOf("user:*", "group:*");
|
||||||
|
|
||||||
|
AuthenticationInfo authenticationInfo = helper.authenticationInfoBuilder("trillian")
|
||||||
|
.withScope(scope)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PrincipalCollection principals = authenticationInfo.getPrincipals();
|
||||||
|
assertThat(principals.oneByType(Scope.class)).isSameAs(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnAuthenticationInfoWithCredentials() {
|
||||||
|
User user = new User("trillian");
|
||||||
|
when(userDAO.get("trillian")).thenReturn(user);
|
||||||
|
|
||||||
|
AuthenticationInfo authenticationInfo = helper.authenticationInfoBuilder("trillian")
|
||||||
|
.withCredentials("secret")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(authenticationInfo.getCredentials()).isEqualTo("secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnAuthenticationInfoWithCredentialsFromUser() {
|
||||||
|
User user = new User("trillian");
|
||||||
|
user.setPassword("secret");
|
||||||
|
when(userDAO.get("trillian")).thenReturn(user);
|
||||||
|
|
||||||
|
AuthenticationInfo authenticationInfo = helper.authenticationInfoBuilder("trillian").build();
|
||||||
|
|
||||||
|
assertThat(authenticationInfo.getCredentials()).isEqualTo("secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowExceptionWithWrongTypeOfToken() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> helper.getAuthenticationInfo(BearerToken.valueOf("__bearer__")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetAuthenticationInfo() {
|
||||||
|
User user = new User("trillian");
|
||||||
|
when(userDAO.get("trillian")).thenReturn(user);
|
||||||
|
|
||||||
|
AuthenticationInfo authenticationInfo = helper.getAuthenticationInfo(new UsernamePasswordToken("trillian", "secret"));
|
||||||
|
|
||||||
|
PrincipalCollection principals = authenticationInfo.getPrincipals();
|
||||||
|
assertThat(principals.oneByType(User.class)).isSameAs(user);
|
||||||
|
assertThat(principals.oneByType(GroupNames.class)).containsOnly("_authenticated");
|
||||||
|
assertThat(principals.oneByType(Scope.class)).isEmpty();
|
||||||
|
|
||||||
|
assertThat(authenticationInfo.getCredentials()).isNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,7 +141,7 @@ public class JAXBConfigurationEntryStoreTest
|
|||||||
|
|
||||||
assertNotNull(ap);
|
assertNotNull(ap);
|
||||||
assertEquals("tuser4", ap.getName());
|
assertEquals("tuser4", ap.getName());
|
||||||
assertEquals("repository:create", ap.getPermission());
|
assertEquals("repository:create", ap.getPermission().getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
package sonia.scm.it;
|
package sonia.scm.it;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.it.utils.ScmRequests;
|
import sonia.scm.it.utils.ScmRequests;
|
||||||
import sonia.scm.it.utils.TestData;
|
import sonia.scm.it.utils.TestData;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
public class MeITCase {
|
public class MeITCase {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -23,9 +20,6 @@ public class MeITCase {
|
|||||||
.requestIndexResource(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN)
|
.requestIndexResource(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN)
|
||||||
.requestMe()
|
.requestMe()
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
|
||||||
.assertPassword(Assert::assertNull)
|
|
||||||
.assertType(s -> assertThat(s).isEqualTo("xml"))
|
|
||||||
.requestChangePassword(TestData.USER_SCM_ADMIN, newPassword)
|
.requestChangePassword(TestData.USER_SCM_ADMIN, newPassword)
|
||||||
.assertStatusCode(204);
|
.assertStatusCode(204);
|
||||||
// assert password is changed -> login with the new Password than undo changes
|
// assert password is changed -> login with the new Password than undo changes
|
||||||
@@ -33,7 +27,6 @@ public class MeITCase {
|
|||||||
.requestIndexResource(TestData.USER_SCM_ADMIN, newPassword)
|
.requestIndexResource(TestData.USER_SCM_ADMIN, newPassword)
|
||||||
.requestMe()
|
.requestMe()
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))// still admin
|
|
||||||
.requestChangePassword(newPassword, TestData.USER_SCM_ADMIN)
|
.requestChangePassword(newPassword, TestData.USER_SCM_ADMIN)
|
||||||
.assertStatusCode(204);
|
.assertStatusCode(204);
|
||||||
}
|
}
|
||||||
@@ -49,9 +42,6 @@ public class MeITCase {
|
|||||||
.requestIndexResource(username, password)
|
.requestIndexResource(username, password)
|
||||||
.requestMe()
|
.requestMe()
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.FALSE))
|
|
||||||
.assertPassword(Assert::assertNull)
|
|
||||||
.assertType(s -> assertThat(s).isEqualTo("xml"))
|
|
||||||
.requestChangePassword(password, newPassword)
|
.requestChangePassword(password, newPassword)
|
||||||
.assertStatusCode(204);
|
.assertStatusCode(204);
|
||||||
// assert password is changed -> login with the new Password than undo changes
|
// assert password is changed -> login with the new Password than undo changes
|
||||||
@@ -72,9 +62,6 @@ public class MeITCase {
|
|||||||
.requestIndexResource(newUser, password)
|
.requestIndexResource(newUser, password)
|
||||||
.requestMe()
|
.requestMe()
|
||||||
.assertStatusCode(200)
|
.assertStatusCode(200)
|
||||||
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
|
||||||
.assertPassword(Assert::assertNull)
|
|
||||||
.assertType(s -> assertThat(s).isEqualTo(type))
|
|
||||||
.assertPasswordLinkDoesNotExists();
|
.assertPasswordLinkDoesNotExists();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class ScmRequests {
|
|||||||
return new IndexResponse(applyGETRequest(RestUtil.REST_BASE_URL.toString()));
|
return new IndexResponse(applyGETRequest(RestUtil.REST_BASE_URL.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public <SELF extends UserResponse<SELF, T>, T extends ModelResponse> UserResponse<SELF,T> requestUser(String username, String password, String pathParam) {
|
public UserResponse<UserResponse> requestUser(String username, String password, String pathParam) {
|
||||||
setUsername(username);
|
setUsername(username);
|
||||||
setPassword(password);
|
setPassword(password);
|
||||||
return new UserResponse<>(applyGETRequest(RestUtil.REST_BASE_URL.resolve("users/"+pathParam).toString()), null);
|
return new UserResponse<>(applyGETRequest(RestUtil.REST_BASE_URL.resolve("users/"+pathParam).toString()), null);
|
||||||
@@ -195,7 +195,7 @@ public class ScmRequests {
|
|||||||
return new MeResponse<>(applyGETRequestFromLink(response, LINK_ME), this);
|
return new MeResponse<>(applyGETRequestFromLink(response, LINK_ME), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserResponse<? extends UserResponse, IndexResponse> requestUser(String username) {
|
public UserResponse<IndexResponse> requestUser(String username) {
|
||||||
return new UserResponse<>(applyGETRequestFromLinkWithParams(response, LINK_USERS, username), this);
|
return new UserResponse<>(applyGETRequestFromLinkWithParams(response, LINK_USERS, username), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,19 +307,24 @@ public class ScmRequests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MeResponse<PREV extends ModelResponse> extends UserResponse<MeResponse<PREV>, PREV> {
|
public class MeResponse<PREV extends ModelResponse> extends ModelResponse<MeResponse<PREV>, PREV> {
|
||||||
|
|
||||||
|
public static final String LINKS_PASSWORD_HREF = "_links.password.href";
|
||||||
|
|
||||||
public MeResponse(Response response, PREV previousResponse) {
|
public MeResponse(Response response, PREV previousResponse) {
|
||||||
super(response, previousResponse);
|
super(response, previousResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangePasswordResponse<UserResponse> requestChangePassword(String oldPassword, String newPassword) {
|
public MeResponse<PREV> assertPasswordLinkDoesNotExists() {
|
||||||
|
return assertPropertyPathDoesNotExists(LINKS_PASSWORD_HREF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangePasswordResponse<MeResponse> requestChangePassword(String oldPassword, String newPassword) {
|
||||||
return new ChangePasswordResponse<>(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(oldPassword, newPassword)), this);
|
return new ChangePasswordResponse<>(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(oldPassword, newPassword)), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UserResponse<SELF extends UserResponse<SELF, PREV>, PREV extends ModelResponse> extends ModelResponse<SELF, PREV> {
|
public class UserResponse<PREV extends ModelResponse> extends ModelResponse<UserResponse<PREV>, PREV> {
|
||||||
|
|
||||||
public static final String LINKS_PASSWORD_HREF = "_links.password.href";
|
public static final String LINKS_PASSWORD_HREF = "_links.password.href";
|
||||||
|
|
||||||
@@ -327,23 +332,23 @@ public class ScmRequests {
|
|||||||
super(response, previousResponse);
|
super(response, previousResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SELF assertPassword(Consumer<String> assertPassword) {
|
public UserResponse<PREV> assertPassword(Consumer<String> assertPassword) {
|
||||||
return super.assertSingleProperty(assertPassword, "password");
|
return super.assertSingleProperty(assertPassword, "password");
|
||||||
}
|
}
|
||||||
|
|
||||||
public SELF assertType(Consumer<String> assertType) {
|
public UserResponse<PREV> assertType(Consumer<String> assertType) {
|
||||||
return assertSingleProperty(assertType, "type");
|
return assertSingleProperty(assertType, "type");
|
||||||
}
|
}
|
||||||
|
|
||||||
public SELF assertAdmin(Consumer<Boolean> assertAdmin) {
|
public UserResponse<PREV> assertAdmin(Consumer<Boolean> assertAdmin) {
|
||||||
return assertSingleProperty(assertAdmin, "admin");
|
return assertSingleProperty(assertAdmin, "admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
public SELF assertPasswordLinkDoesNotExists() {
|
public UserResponse<PREV> assertPasswordLinkDoesNotExists() {
|
||||||
return assertPropertyPathDoesNotExists(LINKS_PASSWORD_HREF);
|
return assertPropertyPathDoesNotExists(LINKS_PASSWORD_HREF);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SELF assertPasswordLinkExists() {
|
public UserResponse<PREV> assertPasswordLinkExists() {
|
||||||
return assertPropertyPathExists(LINKS_PASSWORD_HREF);
|
return assertPropertyPathExists(LINKS_PASSWORD_HREF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.dd7s
|
||||||
|
|
||||||
|
http://bitbucket.org/sdorra/scm-manager
|
||||||
|
|
||||||
|
|
||||||
|
-->
|
||||||
|
<permissions>
|
||||||
|
|
||||||
|
<permission>
|
||||||
|
<value>configuration:read:git</value>
|
||||||
|
</permission>
|
||||||
|
<permission>
|
||||||
|
<value>configuration:write:git</value>
|
||||||
|
</permission>
|
||||||
|
|
||||||
|
</permissions>
|
||||||
@@ -33,5 +33,21 @@
|
|||||||
},
|
},
|
||||||
"success": "Default branch changed!"
|
"success": "Default branch changed!"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"permissions" : {
|
||||||
|
"configuration": {
|
||||||
|
"read": {
|
||||||
|
"git": {
|
||||||
|
"displayName": "Read git configuration",
|
||||||
|
"description": "May read the git configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"git": {
|
||||||
|
"displayName": "Write git configuration",
|
||||||
|
"description": "May change the git configuration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import sonia.scm.repository.Modifications;
|
|||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
import sonia.scm.repository.spi.javahg.HgLogChangesetCommand;
|
||||||
|
|
||||||
import java.text.MessageFormat;
|
|
||||||
|
|
||||||
public class HgModificationsCommand extends AbstractCommand implements ModificationsCommand {
|
public class HgModificationsCommand extends AbstractCommand implements ModificationsCommand {
|
||||||
|
|
||||||
HgModificationsCommand(HgCommandContext context, Repository repository) {
|
HgModificationsCommand(HgCommandContext context, Repository repository) {
|
||||||
@@ -17,8 +15,7 @@ public class HgModificationsCommand extends AbstractCommand implements Modificat
|
|||||||
public Modifications getModifications(String revision) {
|
public Modifications getModifications(String revision) {
|
||||||
com.aragost.javahg.Repository repository = open();
|
com.aragost.javahg.Repository repository = open();
|
||||||
HgLogChangesetCommand hgLogChangesetCommand = HgLogChangesetCommand.on(repository, getContext().getConfig());
|
HgLogChangesetCommand hgLogChangesetCommand = HgLogChangesetCommand.on(repository, getContext().getConfig());
|
||||||
int hgRevision = hgLogChangesetCommand.rev(revision).singleRevision();
|
Modifications modifications = hgLogChangesetCommand.rev(revision).extractModifications();
|
||||||
Modifications modifications = hgLogChangesetCommand.rev(MessageFormat.format("{0}:{0}", hgRevision)).extractModifications();
|
|
||||||
modifications.setRevision(revision);
|
modifications.setRevision(revision);
|
||||||
return modifications;
|
return modifications;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
* <p>
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions are met:
|
* modification, are permitted provided that the following conditions are met:
|
||||||
*
|
* <p>
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
* this list of conditions and the following disclaimer.
|
* this list of conditions and the following disclaimer.
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
* and/or other materials provided with the distribution.
|
* and/or other materials provided with the distribution.
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
* contributors may be used to endorse or promote products derived from this
|
* contributors may be used to endorse or promote products derived from this
|
||||||
* software without specific prior written permission.
|
* software without specific prior written permission.
|
||||||
*
|
* <p>
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
@@ -24,99 +24,64 @@
|
|||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* 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
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*
|
* <p>
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.repository.spi.javahg;
|
package sonia.scm.repository.spi.javahg;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.aragost.javahg.Repository;
|
import com.aragost.javahg.Repository;
|
||||||
import com.aragost.javahg.internals.HgInputStream;
|
import com.aragost.javahg.internals.HgInputStream;
|
||||||
import com.aragost.javahg.internals.Utils;
|
import com.aragost.javahg.internals.Utils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class HgLogChangesetCommand extends AbstractChangesetCommand
|
public class HgLogChangesetCommand extends AbstractChangesetCommand {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
private static final Logger LOG = LoggerFactory.getLogger(HgLogChangesetCommand.class);
|
||||||
* Constructs ...
|
|
||||||
*
|
private HgLogChangesetCommand(Repository repository, HgConfig config) {
|
||||||
*
|
|
||||||
* @param repository
|
|
||||||
* @param config
|
|
||||||
*/
|
|
||||||
private HgLogChangesetCommand(Repository repository, HgConfig config)
|
|
||||||
{
|
|
||||||
super(repository, config);
|
super(repository, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
public static HgLogChangesetCommand on(Repository repository, HgConfig config) {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repository
|
|
||||||
* @param config
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static HgLogChangesetCommand on(Repository repository, HgConfig config)
|
|
||||||
{
|
|
||||||
return new HgLogChangesetCommand(repository, config);
|
return new HgLogChangesetCommand(repository, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
public HgLogChangesetCommand branch(String branch) {
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param branch
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public HgLogChangesetCommand branch(String branch)
|
|
||||||
{
|
|
||||||
cmdAppend("-b", branch);
|
cmdAppend("-b", branch);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
public List<Changeset> execute(String... files) {
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param files
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public List<Changeset> execute(String... files)
|
|
||||||
{
|
|
||||||
return readListFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH));
|
return readListFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract Modifications from the Repository files
|
|
||||||
*
|
|
||||||
* @param files repo files
|
|
||||||
* @return modifications
|
|
||||||
*/
|
|
||||||
public Modifications extractModifications(String... files) {
|
public Modifications extractModifications(String... files) {
|
||||||
return readModificationsFromStream(getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH));
|
HgInputStream hgInputStream = getHgInputStream(files, CHANGESET_EAGER_STYLE_PATH);
|
||||||
|
try {
|
||||||
|
return readModificationsFromStream(hgInputStream);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
hgInputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Could not close HgInputStream", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HgInputStream getHgInputStream(String[] files, String changesetStylePath) {
|
HgInputStream getHgInputStream(String[] files, String changesetStylePath) {
|
||||||
@@ -124,93 +89,39 @@ public class HgLogChangesetCommand extends AbstractChangesetCommand
|
|||||||
return launchStream(files);
|
return launchStream(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public HgLogChangesetCommand limit(int limit) {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param limit
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public HgLogChangesetCommand limit(int limit)
|
|
||||||
{
|
|
||||||
cmdAppend("-l", limit);
|
cmdAppend("-l", limit);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
public List<Integer> loadRevisions(String... files) {
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param files
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public List<Integer> loadRevisions(String... files)
|
|
||||||
{
|
|
||||||
return loadRevisionsFromStream(getHgInputStream(files, CHANGESET_LAZY_STYLE_PATH));
|
return loadRevisionsFromStream(getHgInputStream(files, CHANGESET_LAZY_STYLE_PATH));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public HgLogChangesetCommand rev(String... rev) {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param rev
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public HgLogChangesetCommand rev(String... rev)
|
|
||||||
{
|
|
||||||
cmdAppend("-r", rev);
|
cmdAppend("-r", rev);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Changeset single(String... files) {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param files
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Changeset single(String... files)
|
|
||||||
{
|
|
||||||
return Utils.single(execute(files));
|
return Utils.single(execute(files));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public int singleRevision(String... files) {
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param files
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public int singleRevision(String... files)
|
|
||||||
{
|
|
||||||
Integer rev = Utils.single(loadRevisions(files));
|
Integer rev = Utils.single(loadRevisions(files));
|
||||||
|
|
||||||
if (rev == null)
|
if (rev == null) {
|
||||||
{
|
|
||||||
rev = -1;
|
rev = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rev;
|
return rev;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String getCommandName()
|
public String getCommandName() {
|
||||||
{
|
|
||||||
return "log";
|
return "log";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.dd7s
|
||||||
|
|
||||||
|
http://bitbucket.org/sdorra/scm-manager
|
||||||
|
|
||||||
|
|
||||||
|
-->
|
||||||
|
<permissions>
|
||||||
|
|
||||||
|
<permission>
|
||||||
|
<value>configuration:read:hg</value>
|
||||||
|
</permission>
|
||||||
|
<permission>
|
||||||
|
<value>configuration:write:hg</value>
|
||||||
|
</permission>
|
||||||
|
|
||||||
|
</permissions>
|
||||||
@@ -24,5 +24,21 @@
|
|||||||
"disabledHelpText": "Enable or disable the Mercurial plugin.",
|
"disabledHelpText": "Enable or disable the Mercurial plugin.",
|
||||||
"required": "This configuration value is required"
|
"required": "This configuration value is required"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"permissions" : {
|
||||||
|
"configuration": {
|
||||||
|
"read": {
|
||||||
|
"hg": {
|
||||||
|
"displayName": "Read Mercurial configuration",
|
||||||
|
"description": "May read the Mercurial configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"hg": {
|
||||||
|
"displayName": "Write Mercurial configuration",
|
||||||
|
"description": "May change the Mercurial configuration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.dd7s
|
||||||
|
|
||||||
|
http://bitbucket.org/sdorra/scm-manager
|
||||||
|
|
||||||
|
|
||||||
|
-->
|
||||||
|
<permissions>
|
||||||
|
|
||||||
|
<permission>
|
||||||
|
<value>configuration:read:svn</value>
|
||||||
|
</permission>
|
||||||
|
<permission>
|
||||||
|
<value>configuration:write:svn</value>
|
||||||
|
</permission>
|
||||||
|
|
||||||
|
</permissions>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"scm-svn-plugin": {
|
"scm-svn-plugin": {
|
||||||
"information": {
|
"information": {
|
||||||
"checkout" : "Checkout repository"
|
"checkout": "Checkout repository"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"link": "Subversion",
|
"link": "Subversion",
|
||||||
@@ -22,5 +22,21 @@
|
|||||||
"disabledHelpText": "Enable or disable the Git plugin",
|
"disabledHelpText": "Enable or disable the Git plugin",
|
||||||
"required": "This configuration value is required"
|
"required": "This configuration value is required"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"configuration": {
|
||||||
|
"read": {
|
||||||
|
"svn": {
|
||||||
|
"displayName": "Read Subversion configuration",
|
||||||
|
"description": "May read the Subversion configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"svn": {
|
||||||
|
"displayName": "Write Subversion configuration",
|
||||||
|
"description": "May change the Subversion configuration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package sonia.scm.store;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class InMemoryConfigurationEntryStore<V> implements ConfigurationEntryStore<V> {
|
||||||
|
|
||||||
|
private final Map<String, V> values = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> getMatchingValues(Predicate<V> predicate) {
|
||||||
|
return values.values().stream().filter(predicate).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String put(V item) {
|
||||||
|
String key = UUID.randomUUID().toString();
|
||||||
|
values.put(key, item);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String id, V item) {
|
||||||
|
values.put(id, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, V> getAll() {
|
||||||
|
return Collections.unmodifiableMap(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
values.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String id) {
|
||||||
|
values.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(String id) {
|
||||||
|
return values.get(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package sonia.scm.store;
|
||||||
|
|
||||||
|
public class InMemoryConfigurationEntryStoreFactory implements ConfigurationEntryStoreFactory {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ConfigurationEntryStore store;
|
||||||
|
|
||||||
|
public static ConfigurationEntryStoreFactory create() {
|
||||||
|
return new InMemoryConfigurationEntryStoreFactory(new InMemoryConfigurationEntryStore());
|
||||||
|
}
|
||||||
|
|
||||||
|
public InMemoryConfigurationEntryStoreFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public InMemoryConfigurationEntryStoreFactory(ConfigurationEntryStore store) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> ConfigurationEntryStore<T> getStore(TypedStoreParameters<T> storeParameters) {
|
||||||
|
if (store != null) {
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
return new InMemoryConfigurationEntryStore<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import {
|
import RemoveEntryOfTableButton from "../buttons/RemoveEntryOfTableButton";
|
||||||
RemoveEntryOfTableButton,
|
|
||||||
LabelWithHelpIcon
|
|
||||||
} from "@scm-manager/ui-components";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
members: string[],
|
members: string[],
|
||||||
@@ -19,10 +16,6 @@ class MemberNameTable extends React.Component<Props, State> {
|
|||||||
const { t } = this.props;
|
const { t } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LabelWithHelpIcon
|
|
||||||
label={t("group.members")}
|
|
||||||
helpText={t("group-form.help.memberHelpText")}
|
|
||||||
/>
|
|
||||||
<table className="table is-hoverable is-fullwidth">
|
<table className="table is-hoverable is-fullwidth">
|
||||||
<tbody>
|
<tbody>
|
||||||
{this.props.members.map(member => {
|
{this.props.members.map(member => {
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
export { default as AddEntryToTableField } from "./AddEntryToTableField.js";
|
export { default as AddEntryToTableField } from "./AddEntryToTableField.js";
|
||||||
export { default as AutocompleteAddEntryToTableField } from "./AutocompleteAddEntryToTableField.js";
|
export { default as AutocompleteAddEntryToTableField } from "./AutocompleteAddEntryToTableField.js";
|
||||||
|
export { default as MemberNameTable } from "./MemberNameTable.js";
|
||||||
export { default as Checkbox } from "./Checkbox.js";
|
export { default as Checkbox } from "./Checkbox.js";
|
||||||
export { default as InputField } from "./InputField.js";
|
export { default as InputField } from "./InputField.js";
|
||||||
export { default as Select } from "./Select.js";
|
export { default as Select } from "./Select.js";
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ export type Me = {
|
|||||||
name: string,
|
name: string,
|
||||||
displayName: string,
|
displayName: string,
|
||||||
mail: string,
|
mail: string,
|
||||||
|
groups: [],
|
||||||
_links: Links
|
_links: Links
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,6 +50,9 @@
|
|||||||
"username": "Username",
|
"username": "Username",
|
||||||
"displayName": "Display Name",
|
"displayName": "Display Name",
|
||||||
"mail": "E-Mail",
|
"mail": "E-Mail",
|
||||||
|
"groups": "Groups",
|
||||||
|
"information": "Information",
|
||||||
|
"change-password": "Change password",
|
||||||
"error-title": "Error",
|
"error-title": "Error",
|
||||||
"error-subtitle": "Cannot display profile",
|
"error-subtitle": "Cannot display profile",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"informationNavLink": "Information",
|
"informationNavLink": "Information",
|
||||||
"settingsNavLink": "Settings",
|
"settingsNavLink": "Settings",
|
||||||
"editNavLink": "General",
|
"editNavLink": "General",
|
||||||
"permissionsNavLink": "Permissions"
|
"setPermissionsNavLink": "Permissions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"add-group": {
|
"add-group": {
|
||||||
@@ -67,5 +67,8 @@
|
|||||||
"submit": "Yes",
|
"submit": "Yes",
|
||||||
"cancel": "No"
|
"cancel": "No"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"setPasswordNavLink": {
|
||||||
|
"label": "Set permissions"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
scm-ui/public/locales/en/permissions.json
Normal file
6
scm-ui/public/locales/en/permissions.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"setPermissions": {
|
||||||
|
"button": "Set permissions",
|
||||||
|
"setPermissionsSuccessful": "Permissions set successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,8 @@
|
|||||||
"informationNavLink": "Information",
|
"informationNavLink": "Information",
|
||||||
"settingsNavLink": "Settings",
|
"settingsNavLink": "Settings",
|
||||||
"editNavLink": "General",
|
"editNavLink": "General",
|
||||||
"setPasswordNavLink": "Password"
|
"setPasswordNavLink": "Set Password",
|
||||||
|
"setPermissionsNavLink": "Set Permissions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"addUser": {
|
"addUser": {
|
||||||
|
|||||||
@@ -1,53 +1,53 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { Me } from "@scm-manager/ui-types";
|
import type { Me } from "@scm-manager/ui-types";
|
||||||
import { MailLink, AvatarWrapper, AvatarImage } from "@scm-manager/ui-components";
|
import { MailLink, AvatarWrapper, AvatarImage } from "@scm-manager/ui-components";
|
||||||
import { compose } from "redux";
|
import { compose } from "redux";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
me: Me,
|
me: Me,
|
||||||
|
|
||||||
// Context props
|
// Context props
|
||||||
t: string => string
|
t: string => string
|
||||||
};
|
};
|
||||||
type State = {};
|
type State = {};
|
||||||
|
|
||||||
class ProfileInfo extends React.Component<Props, State> {
|
class ProfileInfo extends React.Component<Props, State> {
|
||||||
render() {
|
render() {
|
||||||
const { me, t } = this.props;
|
const { me, t } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="media">
|
<div className="media">
|
||||||
<AvatarWrapper>
|
<AvatarWrapper>
|
||||||
<figure className="media-left">
|
<figure className="media-left">
|
||||||
<p className="image is-64x64">
|
<p className="image is-64x64">
|
||||||
<AvatarImage person={ me }/>
|
<AvatarImage person={ me }/>
|
||||||
</p>
|
</p>
|
||||||
</figure>
|
</figure>
|
||||||
</AvatarWrapper>
|
</AvatarWrapper>
|
||||||
<div className="media-content">
|
<div className="media-content">
|
||||||
<table className="table">
|
<table className="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="has-text-weight-semibold">{t("profile.username")}</td>
|
<td className="has-text-weight-semibold">{t("profile.username")}</td>
|
||||||
<td>{me.name}</td>
|
<td>{me.name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="has-text-weight-semibold">{t("profile.displayName")}</td>
|
<td className="has-text-weight-semibold">{t("profile.displayName")}</td>
|
||||||
<td>{me.displayName}</td>
|
<td>{me.displayName}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="has-text-weight-semibold">{t("profile.mail")}</td>
|
<td className="has-text-weight-semibold">{t("profile.mail")}</td>
|
||||||
<td>
|
<td>
|
||||||
<MailLink address={me.mail} />
|
<MailLink address={me.mail} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(translate("commons"))(ProfileInfo);
|
export default compose(translate("commons"))(ProfileInfo);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import React from "react";
|
|||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
AutocompleteAddEntryToTableField,
|
AutocompleteAddEntryToTableField,
|
||||||
|
LabelWithHelpIcon,
|
||||||
|
MemberNameTable,
|
||||||
InputField,
|
InputField,
|
||||||
SubmitButton,
|
SubmitButton,
|
||||||
Textarea
|
Textarea
|
||||||
@@ -10,7 +12,6 @@ import {
|
|||||||
import type { Group, SelectValue } from "@scm-manager/ui-types";
|
import type { Group, SelectValue } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
import * as validator from "./groupValidation";
|
import * as validator from "./groupValidation";
|
||||||
import MemberNameTable from "./MemberNameTable";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
@@ -97,6 +98,10 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
validationError={false}
|
validationError={false}
|
||||||
helpText={t("group-form.help.descriptionHelpText")}
|
helpText={t("group-form.help.descriptionHelpText")}
|
||||||
/>
|
/>
|
||||||
|
<LabelWithHelpIcon
|
||||||
|
label={t("group.members")}
|
||||||
|
helpText={t("group-form.help.memberHelpText")}
|
||||||
|
/>
|
||||||
<MemberNameTable
|
<MemberNameTable
|
||||||
members={group.members}
|
members={group.members}
|
||||||
memberListChanged={this.memberListChanged}
|
memberListChanged={this.memberListChanged}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import type { Group } from "@scm-manager/ui-types";
|
||||||
|
import { NavLink } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
t: string => string,
|
||||||
|
group: Group,
|
||||||
|
permissionsUrl: String
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChangePermissionNavLink extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { t, permissionsUrl } = this.props;
|
||||||
|
|
||||||
|
if (!this.hasPermissionToSetPermission()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <NavLink label={t("singleGroup.menu.setPermissionsNavLink")} to={permissionsUrl} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermissionToSetPermission = () => {
|
||||||
|
return this.props.group._links.permissions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("groups")(ChangePermissionNavLink);
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { default as GeneralGroupNavLink } from "./GeneralGroupNavLink";
|
export { default as GeneralGroupNavLink } from "./GeneralGroupNavLink";
|
||||||
|
export { default as SetPermissionsNavLink } from "./SetPermissionsNavLink";
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import {
|
|||||||
import { Route } from "react-router";
|
import { Route } from "react-router";
|
||||||
import { Details } from "./../components/table";
|
import { Details } from "./../components/table";
|
||||||
import {
|
import {
|
||||||
GeneralGroupNavLink
|
GeneralGroupNavLink,
|
||||||
|
SetPermissionsNavLink
|
||||||
} from "./../components/navLinks";
|
} from "./../components/navLinks";
|
||||||
import type { Group } from "@scm-manager/ui-types";
|
import type { Group } from "@scm-manager/ui-types";
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
@@ -27,6 +28,8 @@ import {
|
|||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import EditGroup from "./EditGroup";
|
import EditGroup from "./EditGroup";
|
||||||
import { getGroupsLink } from "../../modules/indexResource";
|
import { getGroupsLink } from "../../modules/indexResource";
|
||||||
|
import SetPermissions from "../../permissions/components/SetPermissions";
|
||||||
|
import {ExtensionPoint} from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string,
|
name: string,
|
||||||
@@ -79,6 +82,11 @@ class SingleGroup extends React.Component<Props> {
|
|||||||
|
|
||||||
const url = this.matchedUrl();
|
const url = this.matchedUrl();
|
||||||
|
|
||||||
|
const extensionProps = {
|
||||||
|
group,
|
||||||
|
url
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title={group.name}>
|
<Page title={group.name}>
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
@@ -93,6 +101,18 @@ class SingleGroup extends React.Component<Props> {
|
|||||||
exact
|
exact
|
||||||
component={() => <EditGroup group={group} />}
|
component={() => <EditGroup group={group} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path={`${url}/permissions`}
|
||||||
|
exact
|
||||||
|
component={() => (
|
||||||
|
<SetPermissions selectedPermissionsLink={group._links.permissions} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<ExtensionPoint
|
||||||
|
name="group.route"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<Navigation>
|
<Navigation>
|
||||||
@@ -101,6 +121,11 @@ class SingleGroup extends React.Component<Props> {
|
|||||||
to={`${url}`}
|
to={`${url}`}
|
||||||
label={t("singleGroup.menu.informationNavLink")}
|
label={t("singleGroup.menu.informationNavLink")}
|
||||||
/>
|
/>
|
||||||
|
<ExtensionPoint
|
||||||
|
name="group.navigation"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
<SubNavigation
|
<SubNavigation
|
||||||
to={`${url}/settings/general`}
|
to={`${url}/settings/general`}
|
||||||
label={t("singleGroup.menu.settingsNavLink")}
|
label={t("singleGroup.menu.settingsNavLink")}
|
||||||
@@ -109,6 +134,10 @@ class SingleGroup extends React.Component<Props> {
|
|||||||
group={group}
|
group={group}
|
||||||
editUrl={`${url}/settings/general`}
|
editUrl={`${url}/settings/general`}
|
||||||
/>
|
/>
|
||||||
|
<SetPermissionsNavLink
|
||||||
|
group={group}
|
||||||
|
permissionsUrl={`${url}/permissions`}
|
||||||
|
/>
|
||||||
</SubNavigation>
|
</SubNavigation>
|
||||||
</Section>
|
</Section>
|
||||||
</Navigation>
|
</Navigation>
|
||||||
|
|||||||
@@ -134,15 +134,6 @@ const callFetchMe = (link: string): Promise<Me> => {
|
|||||||
.get(link)
|
.get(link)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
|
||||||
.then(json => {
|
|
||||||
const { name, displayName, mail, _links } = json;
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
displayName,
|
|
||||||
mail,
|
|
||||||
_links
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
47
scm-ui/src/permissions/components/PermissionCheckbox.js
Normal file
47
scm-ui/src/permissions/components/PermissionCheckbox.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import { Checkbox } from "../../../../scm-ui-components/packages/ui-components/src";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
permission: string,
|
||||||
|
checked: boolean,
|
||||||
|
onChange: (value: boolean, name: string) => void,
|
||||||
|
disabled: boolean,
|
||||||
|
t: string => string
|
||||||
|
};
|
||||||
|
|
||||||
|
class PermissionCheckbox extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { t, permission, checked, onChange, disabled } = this.props;
|
||||||
|
const key = permission.split(":").join(".");
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
name={permission}
|
||||||
|
label={this.translateOrDefault(
|
||||||
|
"permissions." + key + ".displayName",
|
||||||
|
key
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
onChange={onChange}
|
||||||
|
disabled={disabled}
|
||||||
|
helpText={this.translateOrDefault(
|
||||||
|
"permissions." + key + ".description",
|
||||||
|
t("permissions.unknown")
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
translateOrDefault = (key: string, defaultText: string) => {
|
||||||
|
const translation = this.props.t(key);
|
||||||
|
if (translation === key) {
|
||||||
|
return defaultText;
|
||||||
|
} else {
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("plugins")(PermissionCheckbox);
|
||||||
178
scm-ui/src/permissions/components/SetPermissions.js
Normal file
178
scm-ui/src/permissions/components/SetPermissions.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import type { Link } from "@scm-manager/ui-types";
|
||||||
|
import {
|
||||||
|
Notification,
|
||||||
|
ErrorNotification,
|
||||||
|
SubmitButton
|
||||||
|
} from "@scm-manager/ui-components";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import {
|
||||||
|
loadPermissionsForEntity,
|
||||||
|
setPermissions
|
||||||
|
} from "./handlePermissions";
|
||||||
|
import PermissionCheckbox from "./PermissionCheckbox";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { getLink } from "../../modules/indexResource";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
t: string => string,
|
||||||
|
availablePermissionLink: string,
|
||||||
|
selectedPermissionsLink: Link
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
permissions: { [string]: boolean },
|
||||||
|
loading: boolean,
|
||||||
|
error?: Error,
|
||||||
|
permissionsChanged: boolean,
|
||||||
|
permissionsSubmitted: boolean,
|
||||||
|
overwritePermissionsLink?: Link
|
||||||
|
};
|
||||||
|
|
||||||
|
class SetPermissions extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
permissions: {},
|
||||||
|
loading: true,
|
||||||
|
permissionsChanged: false,
|
||||||
|
permissionsSubmitted: false,
|
||||||
|
modifiable: false,
|
||||||
|
overwritePermissionsLink: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingState = () => {
|
||||||
|
this.setState({
|
||||||
|
loading: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setErrorState = (error: Error) => {
|
||||||
|
this.setState({
|
||||||
|
error: error,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setSuccessfulState = () => {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
error: undefined,
|
||||||
|
permissionsSubmitted: true,
|
||||||
|
permissionsChanged: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
loadPermissionsForEntity(
|
||||||
|
this.props.availablePermissionLink,
|
||||||
|
this.props.selectedPermissionsLink.href
|
||||||
|
).then(response => {
|
||||||
|
const { permissions, overwriteLink } = response;
|
||||||
|
this.setState({
|
||||||
|
permissions: permissions,
|
||||||
|
loading: false,
|
||||||
|
overwritePermissionsLink: overwriteLink
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
submit = (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.state.permissions) {
|
||||||
|
const { permissions } = this.state;
|
||||||
|
this.setLoadingState();
|
||||||
|
const selectedPermissions = Object.entries(permissions)
|
||||||
|
.filter(e => e[1])
|
||||||
|
.map(e => e[0]);
|
||||||
|
if (this.state.overwritePermissionsLink) {
|
||||||
|
setPermissions(
|
||||||
|
this.state.overwritePermissionsLink.href,
|
||||||
|
selectedPermissions
|
||||||
|
)
|
||||||
|
.then(result => {
|
||||||
|
this.setSuccessfulState();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.setErrorState(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { t } = this.props;
|
||||||
|
const { loading, permissionsSubmitted, error } = this.state;
|
||||||
|
|
||||||
|
let message = null;
|
||||||
|
|
||||||
|
if (permissionsSubmitted) {
|
||||||
|
message = (
|
||||||
|
<Notification
|
||||||
|
type={"success"}
|
||||||
|
children={t("setPermissions.setPermissionsSuccessful")}
|
||||||
|
onClose={() => this.onClose()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (error) {
|
||||||
|
message = <ErrorNotification error={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={this.submit}>
|
||||||
|
{message}
|
||||||
|
{this.renderPermissions()}
|
||||||
|
<SubmitButton
|
||||||
|
disabled={!this.state.permissionsChanged}
|
||||||
|
loading={loading}
|
||||||
|
label={t("setPermissions.button")}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPermissions = () => {
|
||||||
|
const { overwritePermissionsLink, permissions } = this.state;
|
||||||
|
return Object.keys(permissions).map(p => (
|
||||||
|
<div key={p}>
|
||||||
|
<PermissionCheckbox
|
||||||
|
permission={p}
|
||||||
|
checked={permissions[p]}
|
||||||
|
onChange={this.valueChanged}
|
||||||
|
disabled={!overwritePermissionsLink}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
valueChanged = (value: boolean, name: string) => {
|
||||||
|
this.setState(state => {
|
||||||
|
const newPermissions = state.permissions;
|
||||||
|
newPermissions[name] = value;
|
||||||
|
return {
|
||||||
|
permissions: newPermissions,
|
||||||
|
permissionsChanged: true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.setState({
|
||||||
|
permissionsSubmitted: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const availablePermissionLink = getLink(state, "permissions");
|
||||||
|
return {
|
||||||
|
availablePermissionLink
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(
|
||||||
|
translate("permissions")(SetPermissions)
|
||||||
|
);
|
||||||
33
scm-ui/src/permissions/components/handlePermissions.js
Normal file
33
scm-ui/src/permissions/components/handlePermissions.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//@flow
|
||||||
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
|
export const CONTENT_TYPE_PERMISSIONS =
|
||||||
|
"application/vnd.scmm-permissionCollection+json;v=2";
|
||||||
|
|
||||||
|
export function setPermissions(url: string, permissions: string[]) {
|
||||||
|
return apiClient
|
||||||
|
.put(url, { permissions: permissions }, CONTENT_TYPE_PERMISSIONS)
|
||||||
|
.then(response => {
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadPermissionsForEntity(
|
||||||
|
availableUrl: string,
|
||||||
|
userUrl: string
|
||||||
|
) {
|
||||||
|
return Promise.all([
|
||||||
|
apiClient.get(availableUrl).then(response => {
|
||||||
|
return response.json();
|
||||||
|
}),
|
||||||
|
apiClient.get(userUrl).then(response => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
]).then(values => {
|
||||||
|
const [availablePermissions, checkedPermissions] = values;
|
||||||
|
const permissions = {};
|
||||||
|
availablePermissions.permissions.forEach(p => (permissions[p] = false));
|
||||||
|
checkedPermissions.permissions.forEach(p => (permissions[p] = true));
|
||||||
|
return { permissions, overwriteLink: checkedPermissions._links.overwrite };
|
||||||
|
});
|
||||||
|
}
|
||||||
67
scm-ui/src/permissions/components/handlePermissions.test.js
Normal file
67
scm-ui/src/permissions/components/handlePermissions.test.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//@flow
|
||||||
|
import fetchMock from "fetch-mock";
|
||||||
|
import { loadPermissionsForEntity } from "./handlePermissions";
|
||||||
|
|
||||||
|
describe("load permissions for entity", () => {
|
||||||
|
const AVAILABLE_PERMISSIONS_URL = "/permissions";
|
||||||
|
const USER_PERMISSIONS_URL = "/user/scmadmin/permissions";
|
||||||
|
|
||||||
|
const availablePermissions = `{
|
||||||
|
"permissions": [
|
||||||
|
"repository:read,pull:*",
|
||||||
|
"repository:read,pull,push:*",
|
||||||
|
"repository:*:*"
|
||||||
|
]
|
||||||
|
}`;
|
||||||
|
const userPermissions = `{
|
||||||
|
"permissions": [
|
||||||
|
"repository:read,pull:*"
|
||||||
|
],
|
||||||
|
"_links": {
|
||||||
|
"self": {
|
||||||
|
"href": "/api/v2/users/rene/permissions"
|
||||||
|
},
|
||||||
|
"overwrite": {
|
||||||
|
"href": "/api/v2/users/rene/permissions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fetchMock.getOnce(
|
||||||
|
"/api/v2" + AVAILABLE_PERMISSIONS_URL,
|
||||||
|
availablePermissions
|
||||||
|
);
|
||||||
|
fetchMock.getOnce("/api/v2" + USER_PERMISSIONS_URL, userPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fetchMock.reset();
|
||||||
|
fetchMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return permissions array", done => {
|
||||||
|
loadPermissionsForEntity(
|
||||||
|
AVAILABLE_PERMISSIONS_URL,
|
||||||
|
USER_PERMISSIONS_URL
|
||||||
|
).then(result => {
|
||||||
|
const { permissions } = result;
|
||||||
|
expect(Object.entries(permissions).length).toBe(3);
|
||||||
|
expect(permissions["repository:read,pull:*"]).toBe(true);
|
||||||
|
expect(permissions["repository:read,pull,push:*"]).toBe(false);
|
||||||
|
expect(permissions["repository:*:*"]).toBe(false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return overwrite link", done => {
|
||||||
|
loadPermissionsForEntity(
|
||||||
|
AVAILABLE_PERMISSIONS_URL,
|
||||||
|
USER_PERMISSIONS_URL
|
||||||
|
).then(result => {
|
||||||
|
const { overwriteLink } = result;
|
||||||
|
expect(overwriteLink.href).toBe("/api/v2/users/rene/permissions");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import type { User } from "@scm-manager/ui-types";
|
||||||
|
import { NavLink } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
t: string => string,
|
||||||
|
user: User,
|
||||||
|
permissionsUrl: String
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChangePermissionNavLink extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { t, permissionsUrl } = this.props;
|
||||||
|
|
||||||
|
if (!this.hasPermissionToSetPermission()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <NavLink label={t("singleUser.menu.setPermissionsNavLink")} to={permissionsUrl} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermissionToSetPermission = () => {
|
||||||
|
return this.props.user._links.permissions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate("users")(ChangePermissionNavLink);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import "../../../tests/enzyme";
|
||||||
|
import "../../../tests/i18n";
|
||||||
|
import SetPermissionsNavLink from "./SetPermissionsNavLink";
|
||||||
|
|
||||||
|
it("should render nothing, if the permissions link is missing", () => {
|
||||||
|
const user = {
|
||||||
|
_links: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const navLink = shallow(
|
||||||
|
<SetPermissionsNavLink user={user} permissionsUrl="/user/permissions" />
|
||||||
|
);
|
||||||
|
expect(navLink.text()).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render the navLink", () => {
|
||||||
|
const user = {
|
||||||
|
_links: {
|
||||||
|
permissions: {
|
||||||
|
href: "/permissions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const navLink = shallow(
|
||||||
|
<SetPermissionsNavLink user={user} permissionsUrl="/user/permissions" />
|
||||||
|
);
|
||||||
|
expect(navLink.text()).not.toBe("");
|
||||||
|
});
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export { default as GeneralUserNavLink } from "./GeneralUserNavLink";
|
export { default as GeneralUserNavLink } from "./GeneralUserNavLink";
|
||||||
export { default as SetPasswordNavLink } from "./SetPasswordNavLink";
|
export { default as SetPasswordNavLink } from "./SetPasswordNavLink";
|
||||||
|
export { default as SetPermissionsNavLink } from "./SetPermissionsNavLink";
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ import {
|
|||||||
isFetchUserPending,
|
isFetchUserPending,
|
||||||
getFetchUserFailure
|
getFetchUserFailure
|
||||||
} from "../modules/users";
|
} from "../modules/users";
|
||||||
import { GeneralUserNavLink, SetPasswordNavLink } from "./../components/navLinks";
|
import { GeneralUserNavLink, SetPasswordNavLink, SetPermissionsNavLink } from "./../components/navLinks";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import { getUsersLink } from "../../modules/indexResource";
|
import { getUsersLink } from "../../modules/indexResource";
|
||||||
import SetUserPassword from "../components/SetUserPassword";
|
import SetUserPassword from "../components/SetUserPassword";
|
||||||
|
import SetPermissions from "../../permissions/components/SetPermissions";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string,
|
name: string,
|
||||||
@@ -90,6 +91,14 @@ class SingleUser extends React.Component<Props> {
|
|||||||
path={`${url}/settings/password`}
|
path={`${url}/settings/password`}
|
||||||
component={() => <SetUserPassword user={user} />}
|
component={() => <SetUserPassword user={user} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path={`${url}/permissions`}
|
||||||
|
component={() => (
|
||||||
|
<SetPermissions
|
||||||
|
selectedPermissionsLink={user._links.permissions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<Navigation>
|
<Navigation>
|
||||||
@@ -110,6 +119,10 @@ class SingleUser extends React.Component<Props> {
|
|||||||
user={user}
|
user={user}
|
||||||
passwordUrl={`${url}/settings/password`}
|
passwordUrl={`${url}/settings/password`}
|
||||||
/>
|
/>
|
||||||
|
<SetPermissionsNavLink
|
||||||
|
user={user}
|
||||||
|
permissionsUrl={`${url}/settings/permissions`}
|
||||||
|
/>
|
||||||
</SubNavigation>
|
</SubNavigation>
|
||||||
</Section>
|
</Section>
|
||||||
</Navigation>
|
</Navigation>
|
||||||
|
|||||||
@@ -1,298 +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.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
|
||||||
|
|
||||||
import sonia.scm.api.rest.Permission;
|
|
||||||
import sonia.scm.security.AssignedPermission;
|
|
||||||
import sonia.scm.security.SecuritySystem;
|
|
||||||
import sonia.scm.security.StoredAssignedPermission;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
|
||||||
import javax.ws.rs.DELETE;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.PUT;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.PathParam;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.Response.Status;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract base class for global permission resources.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
* @since 1.31
|
|
||||||
*/
|
|
||||||
public abstract class AbstractPermissionResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@link AbstractPermissionResource}.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param securitySystem security system
|
|
||||||
* @param name name of the user or group
|
|
||||||
*/
|
|
||||||
protected AbstractPermissionResource(SecuritySystem securitySystem,
|
|
||||||
String name)
|
|
||||||
{
|
|
||||||
this.securitySystem = securitySystem;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms a {@link Permission} to a {@link AssignedPermission}.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param permission permission object to transform
|
|
||||||
*
|
|
||||||
* @return transformed {@link AssignedPermission}
|
|
||||||
*/
|
|
||||||
protected abstract AssignedPermission transformPermission(
|
|
||||||
Permission permission);
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link Predicate} to filter permissions.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return {@link Predicate} to filter permissions
|
|
||||||
*/
|
|
||||||
protected abstract Predicate<AssignedPermission> getPredicate();
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new permission to the user or group managed by the resource.
|
|
||||||
*
|
|
||||||
* @param uriInfo uri informations
|
|
||||||
* @param permission permission to add
|
|
||||||
*
|
|
||||||
* @return web response
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 201, condition = "creates", additionalHeaders = {
|
|
||||||
@ResponseHeader(name = "Location", description = "uri to new create permission")
|
|
||||||
}),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Response add(@Context UriInfo uriInfo, Permission permission)
|
|
||||||
{
|
|
||||||
AssignedPermission ap = transformPermission(permission);
|
|
||||||
StoredAssignedPermission sap = securitySystem.addPermission(ap);
|
|
||||||
URI uri = uriInfo.getAbsolutePathBuilder().path(sap.getId()).build();
|
|
||||||
|
|
||||||
return Response.created(uri).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a permission from the user or group managed by the resource.
|
|
||||||
*
|
|
||||||
* @param id id of the permission
|
|
||||||
*
|
|
||||||
* @return web response
|
|
||||||
*/
|
|
||||||
@DELETE
|
|
||||||
@Path("{id}")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 204, condition = "success"),
|
|
||||||
@ResponseCode(code = 400, condition = "bad request, permission id does not belong to the user or group"),
|
|
||||||
@ResponseCode(code = 404, condition = "not found, no permission with the specified id available"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
public Response delete(@PathParam("id") String id)
|
|
||||||
{
|
|
||||||
StoredAssignedPermission sap = getPermission(id);
|
|
||||||
|
|
||||||
securitySystem.deletePermission(sap);
|
|
||||||
|
|
||||||
return Response.noContent().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the specified permission on the user or group managed by the resource.
|
|
||||||
*
|
|
||||||
* @param id id of the permission
|
|
||||||
* @param permission updated permission
|
|
||||||
*
|
|
||||||
* @return web response
|
|
||||||
*/
|
|
||||||
@PUT
|
|
||||||
@Path("{id}")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 204, condition = "success"),
|
|
||||||
@ResponseCode(code = 400, condition = "bad request, permission id does not belong to the user or group"),
|
|
||||||
@ResponseCode(code = 404, condition = "not found, no permission with the specified id available"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Response update(@PathParam("id") String id, Permission permission)
|
|
||||||
{
|
|
||||||
StoredAssignedPermission sap = getPermission(id);
|
|
||||||
|
|
||||||
securitySystem.modifyPermission(new StoredAssignedPermission(sap.getId(),
|
|
||||||
transformPermission(permission)));
|
|
||||||
|
|
||||||
return Response.noContent().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link Permission} with the specified id.
|
|
||||||
*
|
|
||||||
* @param id id of the {@link Permission}
|
|
||||||
*
|
|
||||||
* @return {@link Permission} with the specified id
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("{id}")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 204, condition = "success"),
|
|
||||||
@ResponseCode(code = 400, condition = "bad request, permission id does not belong to the user or group"),
|
|
||||||
@ResponseCode(code = 404, condition = "not found, no permission with the specified id available"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Permission get(@PathParam("id") String id)
|
|
||||||
{
|
|
||||||
StoredAssignedPermission sap = getPermission(id);
|
|
||||||
|
|
||||||
return new Permission(sap.getId(), sap.getPermission());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all permissions of the user or group managed by the resource.
|
|
||||||
*
|
|
||||||
* @return all permissions of the user or group
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 204, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public List<Permission> getAll()
|
|
||||||
{
|
|
||||||
return getPermissions(getPredicate());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link StoredAssignedPermission} with the given id.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param id id of the stored permission
|
|
||||||
*
|
|
||||||
* @return {@link StoredAssignedPermission} with the given id
|
|
||||||
*/
|
|
||||||
private StoredAssignedPermission getPermission(String id)
|
|
||||||
{
|
|
||||||
StoredAssignedPermission sap = securitySystem.getPermission(id);
|
|
||||||
|
|
||||||
if (sap == null)
|
|
||||||
{
|
|
||||||
throw new WebApplicationException(Status.NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!getPredicate().apply(sap))
|
|
||||||
{
|
|
||||||
throw new WebApplicationException(Status.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all permissions which matches the given {@link Predicate}.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param predicate predicate for filtering
|
|
||||||
*
|
|
||||||
* @return all permissions which matches the given {@link Predicate}
|
|
||||||
*/
|
|
||||||
private List<Permission> getPermissions(
|
|
||||||
Predicate<AssignedPermission> predicate)
|
|
||||||
{
|
|
||||||
List<StoredAssignedPermission> permissions =
|
|
||||||
securitySystem.getPermissions(predicate);
|
|
||||||
|
|
||||||
return Lists.transform(permissions,
|
|
||||||
new Function<StoredAssignedPermission, Permission>()
|
|
||||||
{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Permission apply(StoredAssignedPermission mgp)
|
|
||||||
{
|
|
||||||
return new Permission(mgp.getId(), mgp.getPermission());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** name of the user or the group */
|
|
||||||
protected String name;
|
|
||||||
|
|
||||||
/** security system */
|
|
||||||
private SecuritySystem securitySystem;
|
|
||||||
}
|
|
||||||
@@ -1,127 +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.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
|
|
||||||
import sonia.scm.api.rest.Permission;
|
|
||||||
import sonia.scm.security.AssignedPermission;
|
|
||||||
import sonia.scm.security.SecuritySystem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource to manage global group permission for a specified group.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
* @since 1.31
|
|
||||||
*/
|
|
||||||
public class GroupPermissionResource extends AbstractPermissionResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new group permissions resource
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param securitySystem security system
|
|
||||||
* @param name name of the group
|
|
||||||
*/
|
|
||||||
public GroupPermissionResource(SecuritySystem securitySystem, String name)
|
|
||||||
{
|
|
||||||
super(securitySystem, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected AssignedPermission transformPermission(Permission permission)
|
|
||||||
{
|
|
||||||
return new AssignedPermission(name, true, permission.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected Predicate<AssignedPermission> getPredicate()
|
|
||||||
{
|
|
||||||
return new GroupPredicate(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Group predicate to filter permissions.
|
|
||||||
*/
|
|
||||||
private static class GroupPredicate implements Predicate<AssignedPermission>
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new group predicate
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name name of the group
|
|
||||||
*/
|
|
||||||
public GroupPredicate(String name)
|
|
||||||
{
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the permission is a group permission and the name is
|
|
||||||
* equals.
|
|
||||||
*
|
|
||||||
* @param input permission
|
|
||||||
*
|
|
||||||
* @return true if the permission is a group permission and the name is
|
|
||||||
* equals
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean apply(AssignedPermission input)
|
|
||||||
{
|
|
||||||
return input.isGroupPermission() && input.getName().equals(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields -------------------------------------------------------------
|
|
||||||
|
|
||||||
/** name of the group */
|
|
||||||
private String name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +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.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
|
|
||||||
import sonia.scm.security.Role;
|
|
||||||
import sonia.scm.security.SecuritySystem;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.PathParam;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource for managing system security permissions.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Path("security/permission")
|
|
||||||
public class SecuritySystemResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param system
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public SecuritySystemResource(SecuritySystem system)
|
|
||||||
{
|
|
||||||
this.system = system;
|
|
||||||
|
|
||||||
// only administrators can use this resource
|
|
||||||
SecurityUtils.getSubject().checkRole(Role.ADMIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns group permission sub resource.
|
|
||||||
*
|
|
||||||
* @param group name of group
|
|
||||||
*
|
|
||||||
* @return sub resource
|
|
||||||
*/
|
|
||||||
@Path("group/{group}")
|
|
||||||
public GroupPermissionResource getGroupSubResource(@PathParam("group") String group)
|
|
||||||
{
|
|
||||||
return new GroupPermissionResource(system, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns user permission sub resource.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param user name of user
|
|
||||||
*
|
|
||||||
* @return sub resource
|
|
||||||
*/
|
|
||||||
@Path("user/{user}")
|
|
||||||
public UserPermissionResource getUserSubResource(@PathParam("user") String user)
|
|
||||||
{
|
|
||||||
return new UserPermissionResource(system, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final SecuritySystem system;
|
|
||||||
}
|
|
||||||
@@ -1,127 +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.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
|
|
||||||
import sonia.scm.api.rest.Permission;
|
|
||||||
import sonia.scm.security.AssignedPermission;
|
|
||||||
import sonia.scm.security.SecuritySystem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource to manage global user permission for a specified user.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
* @since 1.31
|
|
||||||
*/
|
|
||||||
public class UserPermissionResource extends AbstractPermissionResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new user permission resource.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param securitySystem security system
|
|
||||||
* @param name name of the user
|
|
||||||
*/
|
|
||||||
public UserPermissionResource(SecuritySystem securitySystem, String name)
|
|
||||||
{
|
|
||||||
super(securitySystem, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected AssignedPermission transformPermission(Permission permission)
|
|
||||||
{
|
|
||||||
return new AssignedPermission(name, permission.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected Predicate<AssignedPermission> getPredicate()
|
|
||||||
{
|
|
||||||
return new UserPredicate(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User predicate to filter permissions.
|
|
||||||
*/
|
|
||||||
private static class UserPredicate implements Predicate<AssignedPermission>
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new user predicate.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name name of the user
|
|
||||||
*/
|
|
||||||
public UserPredicate(String name)
|
|
||||||
{
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the permission is a user permission and the name is
|
|
||||||
* equals.
|
|
||||||
*
|
|
||||||
* @param input permission
|
|
||||||
*
|
|
||||||
* @return true if the permission is a user permission and the name is
|
|
||||||
* equals
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean apply(AssignedPermission input)
|
|
||||||
{
|
|
||||||
return !input.isGroupPermission() && input.getName().equals(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields -------------------------------------------------------------
|
|
||||||
|
|
||||||
/** name of the user */
|
|
||||||
private String name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import sonia.scm.security.PermissionAssigner;
|
||||||
|
import sonia.scm.security.PermissionDescriptor;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
@Path("v2/permissions")
|
||||||
|
public class GlobalPermissionResource {
|
||||||
|
|
||||||
|
private PermissionAssigner permissionAssigner;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public GlobalPermissionResource(PermissionAssigner permissionAssigner) {
|
||||||
|
this.permissionAssigner = permissionAssigner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(VndMediaType.PERMISSION_COLLECTION)
|
||||||
|
@Path("")
|
||||||
|
public Response getAll() {
|
||||||
|
String[] permissions = permissionAssigner.getAvailablePermissions().stream().map(PermissionDescriptor::getValue).toArray(String[]::new);
|
||||||
|
return Response.ok(new PermissionListDto(permissions)).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
|
import sonia.scm.security.PermissionAssigner;
|
||||||
|
import sonia.scm.security.PermissionDescriptor;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.PUT;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class GroupPermissionResource {
|
||||||
|
|
||||||
|
private final PermissionAssigner permissionAssigner;
|
||||||
|
private final PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public GroupPermissionResource(PermissionAssigner permissionAssigner, PermissionCollectionToDtoMapper permissionCollectionToDtoMapper) {
|
||||||
|
this.permissionAssigner = permissionAssigner;
|
||||||
|
this.permissionCollectionToDtoMapper = permissionCollectionToDtoMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns permissions for a group.
|
||||||
|
*
|
||||||
|
* @param id the id/name of the group
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("")
|
||||||
|
@Produces(VndMediaType.PERMISSION_COLLECTION)
|
||||||
|
@TypeHint(PermissionListDto.class)
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the group"),
|
||||||
|
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
public Response getPermissions(@PathParam("id") String id) {
|
||||||
|
Collection<PermissionDescriptor> permissions = permissionAssigner.readPermissionsForGroup(id);
|
||||||
|
return Response.ok(permissionCollectionToDtoMapper.mapForGroup(permissions, id)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets permissions for a group. Overwrites all existing permissions.
|
||||||
|
*
|
||||||
|
* @param id id of the group to be modified
|
||||||
|
* @param newPermissions New list of permissions for the group
|
||||||
|
*/
|
||||||
|
@PUT
|
||||||
|
@Path("")
|
||||||
|
@Consumes(VndMediaType.PERMISSION_COLLECTION)
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 204, condition = "update success"),
|
||||||
|
@ResponseCode(code = 400, condition = "Invalid body"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current group does not have the correct privilege"),
|
||||||
|
@ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
|
public Response overwritePermissions(@PathParam("id") String id, PermissionListDto newPermissions) {
|
||||||
|
Collection<PermissionDescriptor> permissionDescriptors = Arrays.stream(newPermissions.getPermissions())
|
||||||
|
.map(PermissionDescriptor::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
permissionAssigner.setPermissionsForGroup(id, permissionDescriptors);
|
||||||
|
return Response.noContent().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ import sonia.scm.group.GroupManager;
|
|||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
@@ -24,13 +23,15 @@ public class GroupResource {
|
|||||||
private final GroupToGroupDtoMapper groupToGroupDtoMapper;
|
private final GroupToGroupDtoMapper groupToGroupDtoMapper;
|
||||||
private final GroupDtoToGroupMapper dtoToGroupMapper;
|
private final GroupDtoToGroupMapper dtoToGroupMapper;
|
||||||
private final IdResourceManagerAdapter<Group, GroupDto> adapter;
|
private final IdResourceManagerAdapter<Group, GroupDto> adapter;
|
||||||
|
private final GroupPermissionResource groupPermissionResource;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public GroupResource(GroupManager manager, GroupToGroupDtoMapper groupToGroupDtoMapper,
|
public GroupResource(GroupManager manager, GroupToGroupDtoMapper groupToGroupDtoMapper,
|
||||||
GroupDtoToGroupMapper groupDtoToGroupMapper) {
|
GroupDtoToGroupMapper groupDtoToGroupMapper, GroupPermissionResource groupPermissionResource) {
|
||||||
this.groupToGroupDtoMapper = groupToGroupDtoMapper;
|
this.groupToGroupDtoMapper = groupToGroupDtoMapper;
|
||||||
this.dtoToGroupMapper = groupDtoToGroupMapper;
|
this.dtoToGroupMapper = groupDtoToGroupMapper;
|
||||||
this.adapter = new IdResourceManagerAdapter<>(manager, Group.class);
|
this.adapter = new IdResourceManagerAdapter<>(manager, Group.class);
|
||||||
|
this.groupPermissionResource = groupPermissionResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,4 +101,9 @@ public class GroupResource {
|
|||||||
public Response update(@PathParam("id") String name, @Valid GroupDto group) {
|
public Response update(@PathParam("id") String name, @Valid GroupDto group) {
|
||||||
return adapter.update(name, existing -> dtoToGroupMapper.map(group));
|
return adapter.update(name, existing -> dtoToGroupMapper.map(group));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("permissions")
|
||||||
|
public GroupPermissionResource permissions() {
|
||||||
|
return groupPermissionResource;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.mapstruct.Mapper;
|
|||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
import sonia.scm.group.GroupPermissions;
|
import sonia.scm.group.GroupPermissions;
|
||||||
|
import sonia.scm.security.PermissionPermissions;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -31,6 +32,9 @@ public abstract class GroupToGroupDtoMapper extends BaseMapper<Group, GroupDto>
|
|||||||
if (GroupPermissions.modify(group).isPermitted()) {
|
if (GroupPermissions.modify(group).isPermitted()) {
|
||||||
linksBuilder.single(link("update", resourceLinks.group().update(target.getName())));
|
linksBuilder.single(link("update", resourceLinks.group().update(target.getName())));
|
||||||
}
|
}
|
||||||
|
if (PermissionPermissions.read().isPermitted()) {
|
||||||
|
linksBuilder.single(link("permissions", resourceLinks.groupPermissions().permissions(target.getName())));
|
||||||
|
}
|
||||||
|
|
||||||
appendLinks(new EdisonLinkAppender(linksBuilder), group);
|
appendLinks(new EdisonLinkAppender(linksBuilder), group);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.apache.shiro.SecurityUtils;
|
|||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
import sonia.scm.config.ConfigurationPermissions;
|
import sonia.scm.config.ConfigurationPermissions;
|
||||||
import sonia.scm.group.GroupPermissions;
|
import sonia.scm.group.GroupPermissions;
|
||||||
|
import sonia.scm.security.PermissionPermissions;
|
||||||
import sonia.scm.user.UserPermissions;
|
import sonia.scm.user.UserPermissions;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -52,6 +53,9 @@ public class IndexDtoGenerator extends LinkAppenderMapper {
|
|||||||
builder.single(link("config", resourceLinks.config().self()));
|
builder.single(link("config", resourceLinks.config().self()));
|
||||||
}
|
}
|
||||||
builder.single(link("repositories", resourceLinks.repositoryCollection().self()));
|
builder.single(link("repositories", resourceLinks.repositoryCollection().self()));
|
||||||
|
if (PermissionPermissions.list().isPermitted()) {
|
||||||
|
builder.single(link("permissions", resourceLinks.permissions().self()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ public class MapperModule extends AbstractModule {
|
|||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(UserDtoToUserMapper.class).to(Mappers.getMapper(UserDtoToUserMapper.class).getClass());
|
bind(UserDtoToUserMapper.class).to(Mappers.getMapper(UserDtoToUserMapper.class).getClass());
|
||||||
bind(MeToUserDtoMapper.class).to(Mappers.getMapper(MeToUserDtoMapper.class).getClass());
|
|
||||||
bind(UserToUserDtoMapper.class).to(Mappers.getMapper(UserToUserDtoMapper.class).getClass());
|
bind(UserToUserDtoMapper.class).to(Mappers.getMapper(UserToUserDtoMapper.class).getClass());
|
||||||
bind(UserCollectionToDtoMapper.class);
|
bind(UserCollectionToDtoMapper.class);
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ public class MapperModule extends AbstractModule {
|
|||||||
|
|
||||||
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(PermissionDtoToPermissionMapper.class).to(Mappers.getMapper(PermissionDtoToPermissionMapper.class).getClass());
|
||||||
bind(PermissionToPermissionDtoMapper.class).to(Mappers.getMapper(PermissionToPermissionDtoMapper.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());
|
||||||
bind(ChangesetToParentDtoMapper.class).to(Mappers.getMapper(ChangesetToParentDtoMapper.class).getClass());
|
bind(ChangesetToParentDtoMapper.class).to(Mappers.getMapper(ChangesetToParentDtoMapper.class).getClass());
|
||||||
@@ -46,6 +45,7 @@ public class MapperModule extends AbstractModule {
|
|||||||
bind(MergeResultToDtoMapper.class).to(Mappers.getMapper(MergeResultToDtoMapper.class).getClass());
|
bind(MergeResultToDtoMapper.class).to(Mappers.getMapper(MergeResultToDtoMapper.class).getClass());
|
||||||
|
|
||||||
// no mapstruct required
|
// no mapstruct required
|
||||||
|
bind(MeDtoFactory.class);
|
||||||
bind(UIPluginDtoMapper.class);
|
bind(UIPluginDtoMapper.class);
|
||||||
bind(UIPluginDtoCollectionMapper.class);
|
bind(UIPluginDtoCollectionMapper.class);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class MeDto extends HalRepresentation {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String displayName;
|
||||||
|
private String mail;
|
||||||
|
private List<String> groups;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
|
protected HalRepresentation add(Links links) {
|
||||||
|
return super.add(links);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import sonia.scm.group.GroupNames;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.UserManager;
|
||||||
|
import sonia.scm.user.UserPermissions;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static de.otto.edison.hal.Link.link;
|
||||||
|
import static de.otto.edison.hal.Links.linkingTo;
|
||||||
|
|
||||||
|
public class MeDtoFactory extends LinkAppenderMapper {
|
||||||
|
|
||||||
|
private final ResourceLinks resourceLinks;
|
||||||
|
private final UserManager userManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MeDtoFactory(ResourceLinks resourceLinks, UserManager userManager) {
|
||||||
|
this.resourceLinks = resourceLinks;
|
||||||
|
this.userManager = userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MeDto create() {
|
||||||
|
PrincipalCollection principals = getPrincipalCollection();
|
||||||
|
|
||||||
|
MeDto dto = new MeDto();
|
||||||
|
|
||||||
|
User user = principals.oneByType(User.class);
|
||||||
|
|
||||||
|
mapUserProperties(user, dto);
|
||||||
|
mapGroups(principals, dto);
|
||||||
|
|
||||||
|
appendLinks(user, dto);
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mapGroups(PrincipalCollection principals, MeDto dto) {
|
||||||
|
Iterable<String> groups = principals.oneByType(GroupNames.class);
|
||||||
|
if (groups == null) {
|
||||||
|
groups = Collections.emptySet();
|
||||||
|
}
|
||||||
|
dto.setGroups(ImmutableList.copyOf(groups));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mapUserProperties(User user, MeDto dto) {
|
||||||
|
dto.setName(user.getName());
|
||||||
|
dto.setDisplayName(user.getDisplayName());
|
||||||
|
dto.setMail(user.getMail());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrincipalCollection getPrincipalCollection() {
|
||||||
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
return subject.getPrincipals();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void appendLinks(User user, MeDto target) {
|
||||||
|
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
|
||||||
|
if (UserPermissions.delete(user).isPermitted()) {
|
||||||
|
linksBuilder.single(link("delete", resourceLinks.me().delete(target.getName())));
|
||||||
|
}
|
||||||
|
if (UserPermissions.modify(user).isPermitted()) {
|
||||||
|
linksBuilder.single(link("update", resourceLinks.me().update(target.getName())));
|
||||||
|
}
|
||||||
|
if (userManager.isTypeDefault(user) && UserPermissions.changePassword(user).isPermitted()) {
|
||||||
|
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLinks(new EdisonLinkAppender(linksBuilder), new Me(), user);
|
||||||
|
|
||||||
|
target.add(linksBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,14 +3,11 @@ package sonia.scm.api.v2.resources;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
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.authc.credential.PasswordService;
|
import org.apache.shiro.authc.credential.PasswordService;
|
||||||
import sonia.scm.user.User;
|
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
@@ -28,20 +25,18 @@ import javax.ws.rs.core.UriInfo;
|
|||||||
*/
|
*/
|
||||||
@Path(MeResource.ME_PATH_V2)
|
@Path(MeResource.ME_PATH_V2)
|
||||||
public class MeResource {
|
public class MeResource {
|
||||||
public static final String ME_PATH_V2 = "v2/me/";
|
|
||||||
|
|
||||||
private final MeToUserDtoMapper meToUserDtoMapper;
|
static final String ME_PATH_V2 = "v2/me/";
|
||||||
|
|
||||||
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
private final MeDtoFactory meDtoFactory;
|
||||||
private final PasswordService passwordService;
|
|
||||||
private final UserManager userManager;
|
private final UserManager userManager;
|
||||||
|
private final PasswordService passwordService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MeResource(MeToUserDtoMapper meToUserDtoMapper, UserManager manager, PasswordService passwordService) {
|
public MeResource(MeDtoFactory meDtoFactory, UserManager userManager, PasswordService passwordService) {
|
||||||
this.meToUserDtoMapper = meToUserDtoMapper;
|
this.meDtoFactory = meDtoFactory;
|
||||||
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
this.userManager = userManager;
|
||||||
this.passwordService = passwordService;
|
this.passwordService = passwordService;
|
||||||
this.userManager = manager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,17 +44,15 @@ public class MeResource {
|
|||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("")
|
@Path("")
|
||||||
@Produces(VndMediaType.USER)
|
@Produces(VndMediaType.ME)
|
||||||
@TypeHint(UserDto.class)
|
@TypeHint(MeDto.class)
|
||||||
@StatusCodes({
|
@StatusCodes({
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public Response get(@Context Request request, @Context UriInfo uriInfo) {
|
public Response get(@Context Request request, @Context UriInfo uriInfo) {
|
||||||
|
return Response.ok(meDtoFactory.create()).build();
|
||||||
String id = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
|
||||||
return adapter.get(id, meToUserDtoMapper::map);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,7 +68,10 @@ public class MeResource {
|
|||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
||||||
public Response changePassword(@Valid PasswordChangeDto passwordChange) {
|
public Response changePassword(@Valid PasswordChangeDto passwordChange) {
|
||||||
userManager.changePasswordForLoggedInUser(passwordService.encryptPassword(passwordChange.getOldPassword()), passwordService.encryptPassword(passwordChange.getNewPassword()));
|
userManager.changePasswordForLoggedInUser(
|
||||||
|
passwordService.encryptPassword(passwordChange.getOldPassword()),
|
||||||
|
passwordService.encryptPassword(passwordChange.getNewPassword())
|
||||||
|
);
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
|
||||||
|
|
||||||
import de.otto.edison.hal.Links;
|
|
||||||
import org.mapstruct.AfterMapping;
|
|
||||||
import org.mapstruct.Mapper;
|
|
||||||
import org.mapstruct.MappingTarget;
|
|
||||||
import sonia.scm.user.User;
|
|
||||||
import sonia.scm.user.UserManager;
|
|
||||||
import sonia.scm.user.UserPermissions;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static de.otto.edison.hal.Link.link;
|
|
||||||
import static de.otto.edison.hal.Links.linkingTo;
|
|
||||||
|
|
||||||
@Mapper
|
|
||||||
public abstract class MeToUserDtoMapper extends UserToUserDtoMapper {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private UserManager userManager;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private ResourceLinks resourceLinks;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@AfterMapping
|
|
||||||
protected void appendLinks(User user, @MappingTarget UserDto target) {
|
|
||||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
|
|
||||||
if (UserPermissions.delete(user).isPermitted()) {
|
|
||||||
linksBuilder.single(link("delete", resourceLinks.me().delete(target.getName())));
|
|
||||||
}
|
|
||||||
if (UserPermissions.modify(user).isPermitted()) {
|
|
||||||
linksBuilder.single(link("update", resourceLinks.me().update(target.getName())));
|
|
||||||
}
|
|
||||||
if (userManager.isTypeDefault(user)) {
|
|
||||||
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
|
||||||
}
|
|
||||||
|
|
||||||
appendLinks(new EdisonLinkAppender(linksBuilder), new Me(), user);
|
|
||||||
|
|
||||||
target.add(linksBuilder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,51 +1,48 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import de.otto.edison.hal.Embedded;
|
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import sonia.scm.repository.Repository;
|
import org.mapstruct.Context;
|
||||||
import sonia.scm.repository.RepositoryPermissions;
|
import sonia.scm.security.PermissionDescriptor;
|
||||||
|
import sonia.scm.security.PermissionPermissions;
|
||||||
|
|
||||||
import java.util.List;
|
import javax.inject.Inject;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
|
||||||
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.stream.Collectors.toList;
|
|
||||||
|
|
||||||
public class PermissionCollectionToDtoMapper {
|
public class PermissionCollectionToDtoMapper {
|
||||||
|
|
||||||
private final ResourceLinks resourceLinks;
|
private final ResourceLinks resourceLinks;
|
||||||
private final PermissionToPermissionDtoMapper permissionToPermissionDtoMapper;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PermissionCollectionToDtoMapper(PermissionToPermissionDtoMapper permissionToPermissionDtoMapper, ResourceLinks resourceLinks) {
|
public PermissionCollectionToDtoMapper(ResourceLinks resourceLinks) {
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
this.permissionToPermissionDtoMapper = permissionToPermissionDtoMapper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public HalRepresentation map(Repository repository) {
|
public PermissionListDto mapForUser(Collection<PermissionDescriptor> permissions, String userId) {
|
||||||
List<PermissionDto> permissionDtoList = repository.getPermissions()
|
return map(permissions, userId, resourceLinks.userPermissions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PermissionListDto mapForGroup(Collection<PermissionDescriptor> permissions, String groupId) {
|
||||||
|
return map(permissions, groupId, resourceLinks.groupPermissions());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PermissionListDto map(Collection<PermissionDescriptor> permissions, String id, ResourceLinks.WithPermissionLinks links) {
|
||||||
|
String[] permissionStrings = permissions
|
||||||
.stream()
|
.stream()
|
||||||
.map(permission -> permissionToPermissionDtoMapper.map(permission, repository))
|
.map(PermissionDescriptor::getValue)
|
||||||
.collect(toList());
|
.toArray(String[]::new);
|
||||||
return new HalRepresentation(createLinks(repository), embedDtos(permissionDtoList));
|
PermissionListDto target = new PermissionListDto(permissionStrings);
|
||||||
}
|
|
||||||
|
|
||||||
private Links createLinks(Repository repository) {
|
Links.Builder linksBuilder = linkingTo().self(links.permissions(id));
|
||||||
RepositoryPermissions.permissionRead(repository).check();
|
|
||||||
Links.Builder linksBuilder = linkingTo()
|
if (PermissionPermissions.assign().isPermitted()) {
|
||||||
.with(Links.linkingTo().self(resourceLinks.permission().all(repository.getNamespace(), repository.getName())).build());
|
linksBuilder.single(link("overwrite", links.overwritePermissions(id)));
|
||||||
if (RepositoryPermissions.permissionWrite(repository).isPermitted()) {
|
|
||||||
linksBuilder.single(link("create", resourceLinks.permission().create(repository.getNamespace(), repository.getName())));
|
|
||||||
}
|
}
|
||||||
return linksBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Embedded embedDtos(List<PermissionDto> permissionDtoList) {
|
target.add(linksBuilder.build());
|
||||||
return embeddedBuilder()
|
|
||||||
.with("permissions", permissionDtoList)
|
return target;
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ package sonia.scm.api.v2.resources;
|
|||||||
|
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
import sonia.scm.repository.Permission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public abstract class PermissionDtoToPermissionMapper {
|
public abstract class PermissionDtoToPermissionMapper {
|
||||||
|
|
||||||
public abstract Permission map(PermissionDto permissionDto);
|
public abstract RepositoryPermission map(RepositoryPermissionDto permissionDto);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this method is needed to modify an existing permission object
|
* this method is needed to modify an existing permission object
|
||||||
*
|
*
|
||||||
* @param target the target permission
|
* @param target the target permission
|
||||||
* @param permissionDto the source dto
|
* @param repositoryPermissionDto the source dto
|
||||||
* @return the mapped target permission object
|
* @return the mapped target permission object
|
||||||
*/
|
*/
|
||||||
public abstract void modify(@MappingTarget Permission target, PermissionDto permissionDto);
|
public abstract void modify(@MappingTarget RepositoryPermission target, RepositoryPermissionDto repositoryPermissionDto);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PermissionListDto extends HalRepresentation {
|
||||||
|
|
||||||
|
private String[] permissions;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
|
protected HalRepresentation add(Links links) {
|
||||||
|
return super.add(links);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,14 +8,13 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import sonia.scm.AlreadyExistsException;
|
import sonia.scm.AlreadyExistsException;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.Permission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
import sonia.scm.repository.RepositoryPermissions;
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
@@ -33,24 +32,24 @@ import java.util.function.Predicate;
|
|||||||
import static sonia.scm.AlreadyExistsException.alreadyExists;
|
import static sonia.scm.AlreadyExistsException.alreadyExists;
|
||||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
import static sonia.scm.NotFoundException.notFound;
|
import static sonia.scm.NotFoundException.notFound;
|
||||||
import static sonia.scm.api.v2.resources.PermissionDto.GROUP_PREFIX;
|
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class PermissionRootResource {
|
public class PermissionRootResource {
|
||||||
|
|
||||||
|
|
||||||
private PermissionDtoToPermissionMapper dtoToModelMapper;
|
private PermissionDtoToPermissionMapper dtoToModelMapper;
|
||||||
private PermissionToPermissionDtoMapper modelToDtoMapper;
|
private RepositoryPermissionToRepositoryPermissionDtoMapper modelToDtoMapper;
|
||||||
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper;
|
||||||
private ResourceLinks resourceLinks;
|
private ResourceLinks resourceLinks;
|
||||||
private final RepositoryManager manager;
|
private final RepositoryManager manager;
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PermissionRootResource(PermissionDtoToPermissionMapper dtoToModelMapper, PermissionToPermissionDtoMapper modelToDtoMapper, PermissionCollectionToDtoMapper permissionCollectionToDtoMapper, ResourceLinks resourceLinks, RepositoryManager manager) {
|
public PermissionRootResource(PermissionDtoToPermissionMapper dtoToModelMapper, RepositoryPermissionToRepositoryPermissionDtoMapper modelToDtoMapper, RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper, ResourceLinks resourceLinks, RepositoryManager manager) {
|
||||||
this.dtoToModelMapper = dtoToModelMapper;
|
this.dtoToModelMapper = dtoToModelMapper;
|
||||||
this.modelToDtoMapper = modelToDtoMapper;
|
this.modelToDtoMapper = modelToDtoMapper;
|
||||||
this.permissionCollectionToDtoMapper = permissionCollectionToDtoMapper;
|
this.repositoryPermissionCollectionToDtoMapper = repositoryPermissionCollectionToDtoMapper;
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
}
|
}
|
||||||
@@ -74,7 +73,7 @@ public class PermissionRootResource {
|
|||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
@Consumes(VndMediaType.PERMISSION)
|
@Consumes(VndMediaType.PERMISSION)
|
||||||
@Path("")
|
@Path("")
|
||||||
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid PermissionDto 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();
|
||||||
@@ -82,7 +81,7 @@ public class PermissionRootResource {
|
|||||||
repository.addPermission(dtoToModelMapper.map(permission));
|
repository.addPermission(dtoToModelMapper.map(permission));
|
||||||
manager.modify(repository);
|
manager.modify(repository);
|
||||||
String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission);
|
String urlPermissionName = modelToDtoMapper.getUrlPermissionName(permission);
|
||||||
return Response.created(URI.create(resourceLinks.permission().self(namespace, name, urlPermissionName))).build();
|
return Response.created(URI.create(resourceLinks.repositoryPermission().self(namespace, name, urlPermissionName))).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -101,7 +100,7 @@ public class PermissionRootResource {
|
|||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@Produces(VndMediaType.PERMISSION)
|
@Produces(VndMediaType.PERMISSION)
|
||||||
@TypeHint(PermissionDto.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) {
|
||||||
Repository repository = load(namespace, name);
|
Repository repository = load(namespace, name);
|
||||||
@@ -112,7 +111,7 @@ public class PermissionRootResource {
|
|||||||
.filter(filterPermission(permissionName))
|
.filter(filterPermission(permissionName))
|
||||||
.map(permission -> modelToDtoMapper.map(permission, repository))
|
.map(permission -> modelToDtoMapper.map(permission, repository))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name)))
|
.orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name)))
|
||||||
).build();
|
).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,12 +131,12 @@ public class PermissionRootResource {
|
|||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
@Produces(VndMediaType.PERMISSION)
|
@Produces(VndMediaType.PERMISSION)
|
||||||
@TypeHint(PermissionDto.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) {
|
||||||
Repository repository = load(namespace, name);
|
Repository repository = load(namespace, name);
|
||||||
RepositoryPermissions.permissionRead(repository).check();
|
RepositoryPermissions.permissionRead(repository).check();
|
||||||
return Response.ok(permissionCollectionToDtoMapper.map(repository)).build();
|
return Response.ok(repositoryPermissionCollectionToDtoMapper.map(repository)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -161,23 +160,23 @@ public class PermissionRootResource {
|
|||||||
public Response update(@PathParam("namespace") String namespace,
|
public Response update(@PathParam("namespace") String namespace,
|
||||||
@PathParam("name") String name,
|
@PathParam("name") String name,
|
||||||
@PathParam("permission-name") String permissionName,
|
@PathParam("permission-name") String permissionName,
|
||||||
@Valid PermissionDto permission) {
|
@Valid RepositoryPermissionDto permission) {
|
||||||
log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission);
|
log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission);
|
||||||
Repository repository = load(namespace, name);
|
Repository repository = load(namespace, name);
|
||||||
RepositoryPermissions.permissionWrite(repository).check();
|
RepositoryPermissions.permissionWrite(repository).check();
|
||||||
String extractedPermissionName = getPermissionName(permissionName);
|
String extractedPermissionName = getPermissionName(permissionName);
|
||||||
if (!isPermissionExist(new PermissionDto(extractedPermissionName, isGroupPermission(permissionName)), repository)) {
|
if (!isPermissionExist(new RepositoryPermissionDto(extractedPermissionName, isGroupPermission(permissionName)), repository)) {
|
||||||
throw notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name));
|
throw notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name));
|
||||||
}
|
}
|
||||||
permission.setGroupPermission(isGroupPermission(permissionName));
|
permission.setGroupPermission(isGroupPermission(permissionName));
|
||||||
if (!extractedPermissionName.equals(permission.getName())) {
|
if (!extractedPermissionName.equals(permission.getName())) {
|
||||||
checkPermissionAlreadyExists(permission, repository);
|
checkPermissionAlreadyExists(permission, repository);
|
||||||
}
|
}
|
||||||
Permission existingPermission = repository.getPermissions()
|
RepositoryPermission existingPermission = repository.getPermissions()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(filterPermission(permissionName))
|
.filter(filterPermission(permissionName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> notFound(entity(Permission.class, namespace).in(Repository.class, namespace + "/" + name)));
|
.orElseThrow(() -> notFound(entity(RepositoryPermission.class, namespace).in(Repository.class, namespace + "/" + name)));
|
||||||
dtoToModelMapper.modify(existingPermission, permission);
|
dtoToModelMapper.modify(existingPermission, permission);
|
||||||
manager.modify(repository);
|
manager.modify(repository);
|
||||||
log.info("the permission with name: {} is updated.", permissionName);
|
log.info("the permission with name: {} is updated.", permissionName);
|
||||||
@@ -216,7 +215,7 @@ public class PermissionRootResource {
|
|||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate<Permission> filterPermission(String permissionName) {
|
Predicate<RepositoryPermission> filterPermission(String permissionName) {
|
||||||
return permission -> getPermissionName(permissionName).equals(permission.getName())
|
return permission -> getPermissionName(permissionName).equals(permission.getName())
|
||||||
&&
|
&&
|
||||||
permission.isGroupPermission() == isGroupPermission(permissionName);
|
permission.isGroupPermission() == isGroupPermission(permissionName);
|
||||||
@@ -255,13 +254,13 @@ public class PermissionRootResource {
|
|||||||
* @param repository the repository to be inspected
|
* @param repository the repository to be inspected
|
||||||
* @throws AlreadyExistsException if the permission already exists in the repository
|
* @throws AlreadyExistsException if the permission already exists in the repository
|
||||||
*/
|
*/
|
||||||
private void checkPermissionAlreadyExists(PermissionDto permission, Repository repository) {
|
private void checkPermissionAlreadyExists(RepositoryPermissionDto permission, Repository repository) {
|
||||||
if (isPermissionExist(permission, repository)) {
|
if (isPermissionExist(permission, repository)) {
|
||||||
throw alreadyExists(entity("permission", permission.getName()).in(repository));
|
throw alreadyExists(entity("permission", permission.getName()).in(repository));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPermissionExist(PermissionDto permission, Repository repository) {
|
private boolean isPermissionExist(RepositoryPermissionDto permission, Repository repository) {
|
||||||
return repository.getPermissions()
|
return repository.getPermissions()
|
||||||
.stream()
|
.stream()
|
||||||
.anyMatch(p -> p.getName().equals(permission.getName()) && p.isGroupPermission() == permission.isGroupPermission());
|
.anyMatch(p -> p.getName().equals(permission.getName()) && p.isGroupPermission() == permission.isGroupPermission());
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ 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.Permission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.PermissionType;
|
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;
|
||||||
@@ -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 Permission(currentUser(), PermissionType.OWNER)));
|
repository.setPermissions(singletonList(new RepositoryPermission(currentUser(), PermissionType.OWNER)));
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import de.otto.edison.hal.Embedded;
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||||
|
import static de.otto.edison.hal.Link.link;
|
||||||
|
import static de.otto.edison.hal.Links.linkingTo;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
public class RepositoryPermissionCollectionToDtoMapper {
|
||||||
|
|
||||||
|
private final ResourceLinks resourceLinks;
|
||||||
|
private final RepositoryPermissionToRepositoryPermissionDtoMapper repositoryPermissionToRepositoryPermissionDtoMapper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RepositoryPermissionCollectionToDtoMapper(RepositoryPermissionToRepositoryPermissionDtoMapper repositoryPermissionToRepositoryPermissionDtoMapper, ResourceLinks resourceLinks) {
|
||||||
|
this.resourceLinks = resourceLinks;
|
||||||
|
this.repositoryPermissionToRepositoryPermissionDtoMapper = repositoryPermissionToRepositoryPermissionDtoMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HalRepresentation map(Repository repository) {
|
||||||
|
List<RepositoryPermissionDto> repositoryPermissionDtoList = repository.getPermissions()
|
||||||
|
.stream()
|
||||||
|
.map(permission -> repositoryPermissionToRepositoryPermissionDtoMapper.map(permission, repository))
|
||||||
|
.collect(toList());
|
||||||
|
return new HalRepresentation(createLinks(repository), embedDtos(repositoryPermissionDtoList));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Links createLinks(Repository repository) {
|
||||||
|
RepositoryPermissions.permissionRead(repository).check();
|
||||||
|
Links.Builder linksBuilder = linkingTo()
|
||||||
|
.with(Links.linkingTo().self(resourceLinks.repositoryPermission().all(repository.getNamespace(), repository.getName())).build());
|
||||||
|
if (RepositoryPermissions.permissionWrite(repository).isPermitted()) {
|
||||||
|
linksBuilder.single(link("create", resourceLinks.repositoryPermission().create(repository.getNamespace(), repository.getName())));
|
||||||
|
}
|
||||||
|
return linksBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Embedded embedDtos(List<RepositoryPermissionDto> repositoryPermissionDtoList) {
|
||||||
|
return embeddedBuilder()
|
||||||
|
.with("permissions", repositoryPermissionDtoList)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import javax.validation.constraints.Pattern;
|
|||||||
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
|
||||||
public class PermissionDto extends HalRepresentation {
|
public class RepositoryPermissionDto extends HalRepresentation {
|
||||||
|
|
||||||
public static final String GROUP_PREFIX = "@";
|
public static final String GROUP_PREFIX = "@";
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ public class PermissionDto extends HalRepresentation {
|
|||||||
|
|
||||||
private boolean groupPermission = false;
|
private boolean groupPermission = false;
|
||||||
|
|
||||||
public PermissionDto(String permissionName, boolean groupPermission) {
|
public RepositoryPermissionDto(String permissionName, boolean groupPermission) {
|
||||||
name = permissionName;
|
name = permissionName;
|
||||||
this.groupPermission = groupPermission;
|
this.groupPermission = groupPermission;
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ import org.mapstruct.Context;
|
|||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
import sonia.scm.repository.Permission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryPermissions;
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
|
|
||||||
@@ -16,16 +16,16 @@ import java.util.Optional;
|
|||||||
|
|
||||||
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 sonia.scm.api.v2.resources.PermissionDto.GROUP_PREFIX;
|
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public abstract class PermissionToPermissionDtoMapper {
|
public abstract class RepositoryPermissionToRepositoryPermissionDtoMapper {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ResourceLinks resourceLinks;
|
private ResourceLinks resourceLinks;
|
||||||
|
|
||||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||||
public abstract PermissionDto map(Permission permission, @Context Repository repository);
|
public abstract RepositoryPermissionDto map(RepositoryPermission permission, @Context Repository repository);
|
||||||
|
|
||||||
|
|
||||||
@BeforeMapping
|
@BeforeMapping
|
||||||
@@ -40,20 +40,20 @@ public abstract class PermissionToPermissionDtoMapper {
|
|||||||
* @param repository the repository
|
* @param repository the repository
|
||||||
*/
|
*/
|
||||||
@AfterMapping
|
@AfterMapping
|
||||||
void appendLinks(@MappingTarget PermissionDto target, @Context Repository repository) {
|
void appendLinks(@MappingTarget RepositoryPermissionDto target, @Context Repository repository) {
|
||||||
String permissionName = getUrlPermissionName(target);
|
String permissionName = getUrlPermissionName(target);
|
||||||
Links.Builder linksBuilder = linkingTo()
|
Links.Builder linksBuilder = linkingTo()
|
||||||
.self(resourceLinks.permission().self(repository.getNamespace(), repository.getName(), permissionName));
|
.self(resourceLinks.repositoryPermission().self(repository.getNamespace(), repository.getName(), permissionName));
|
||||||
if (RepositoryPermissions.permissionWrite(repository).isPermitted()) {
|
if (RepositoryPermissions.permissionWrite(repository).isPermitted()) {
|
||||||
linksBuilder.single(link("update", resourceLinks.permission().update(repository.getNamespace(), repository.getName(), permissionName)));
|
linksBuilder.single(link("update", resourceLinks.repositoryPermission().update(repository.getNamespace(), repository.getName(), permissionName)));
|
||||||
linksBuilder.single(link("delete", resourceLinks.permission().delete(repository.getNamespace(), repository.getName(), permissionName)));
|
linksBuilder.single(link("delete", resourceLinks.repositoryPermission().delete(repository.getNamespace(), repository.getName(), permissionName)));
|
||||||
}
|
}
|
||||||
target.add(linksBuilder.build());
|
target.add(linksBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUrlPermissionName(PermissionDto permissionDto) {
|
public String getUrlPermissionName(RepositoryPermissionDto repositoryPermissionDto) {
|
||||||
return Optional.of(permissionDto.getName())
|
return Optional.of(repositoryPermissionDto.getName())
|
||||||
.filter(p -> !permissionDto.isGroupPermission())
|
.filter(p -> !repositoryPermissionDto.isGroupPermission())
|
||||||
.orElse(GROUP_PREFIX + permissionDto.getName());
|
.orElse(GROUP_PREFIX + repositoryPermissionDto.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
|
|||||||
}
|
}
|
||||||
if (RepositoryPermissions.modify(repository).isPermitted()) {
|
if (RepositoryPermissions.modify(repository).isPermitted()) {
|
||||||
linksBuilder.single(link("update", resourceLinks.repository().update(target.getNamespace(), target.getName())));
|
linksBuilder.single(link("update", resourceLinks.repository().update(target.getNamespace(), target.getName())));
|
||||||
linksBuilder.single(link("permissions", resourceLinks.permission().all(target.getNamespace(), target.getName())));
|
linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(target.getNamespace(), target.getName())));
|
||||||
}
|
}
|
||||||
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
|
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
|
||||||
if (RepositoryPermissions.pull(repository).isPermitted()) {
|
if (RepositoryPermissions.pull(repository).isPermitted()) {
|
||||||
|
|||||||
@@ -96,6 +96,52 @@ class ResourceLinks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WithPermissionLinks {
|
||||||
|
String permissions(String name);
|
||||||
|
|
||||||
|
String overwritePermissions(String name);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserPermissionLinks userPermissions() {
|
||||||
|
return new UserPermissionLinks(scmPathInfoStore.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class UserPermissionLinks implements WithPermissionLinks {
|
||||||
|
private final LinkBuilder userPermissionLinkBuilder;
|
||||||
|
|
||||||
|
UserPermissionLinks(ScmPathInfo pathInfo) {
|
||||||
|
this.userPermissionLinkBuilder = new LinkBuilder(pathInfo, UserRootResource.class, UserResource.class, UserPermissionResource.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String permissions(String name) {
|
||||||
|
return userPermissionLinkBuilder.method("getUserResource").parameters(name).method("permissions").parameters().method("getPermissions").parameters().href();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String overwritePermissions(String name) {
|
||||||
|
return userPermissionLinkBuilder.method("getUserResource").parameters(name).method("permissions").parameters().method("overwritePermissions").parameters().href();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupPermissionLinks groupPermissions() {
|
||||||
|
return new GroupPermissionLinks(scmPathInfoStore.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class GroupPermissionLinks implements WithPermissionLinks {
|
||||||
|
private final LinkBuilder groupPermissionLinkBuilder;
|
||||||
|
|
||||||
|
GroupPermissionLinks(ScmPathInfo pathInfo) {
|
||||||
|
this.groupPermissionLinkBuilder = new LinkBuilder(pathInfo, GroupRootResource.class, GroupResource.class, GroupPermissionResource.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String permissions(String name) {
|
||||||
|
return groupPermissionLinkBuilder.method("getGroupResource").parameters(name).method("permissions").parameters().method("getPermissions").parameters().href();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String overwritePermissions(String name) {
|
||||||
|
return groupPermissionLinkBuilder.method("getGroupResource").parameters(name).method("permissions").parameters().method("overwritePermissions").parameters().href();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MeLinks me() {
|
MeLinks me() {
|
||||||
return new MeLinks(scmPathInfoStore.get(), this.user());
|
return new MeLinks(scmPathInfoStore.get(), this.user());
|
||||||
}
|
}
|
||||||
@@ -459,14 +505,15 @@ class ResourceLinks {
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
public PermissionLinks permission() {
|
|
||||||
return new PermissionLinks(scmPathInfoStore.get());
|
public RepositoryPermissionLinks repositoryPermission() {
|
||||||
|
return new RepositoryPermissionLinks(scmPathInfoStore.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
static class PermissionLinks {
|
static class RepositoryPermissionLinks {
|
||||||
private final LinkBuilder permissionLinkBuilder;
|
private final LinkBuilder permissionLinkBuilder;
|
||||||
|
|
||||||
PermissionLinks(ScmPathInfo pathInfo) {
|
RepositoryPermissionLinks(ScmPathInfo pathInfo) {
|
||||||
permissionLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, PermissionRootResource.class);
|
permissionLinkBuilder = new LinkBuilder(pathInfo, RepositoryRootResource.class, RepositoryResource.class, PermissionRootResource.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,4 +633,20 @@ class ResourceLinks {
|
|||||||
return mergeLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("merge").parameters().method("dryRun").parameters().href();
|
return mergeLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("merge").parameters().method("dryRun").parameters().href();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PermissionsLinks permissions() {
|
||||||
|
return new PermissionsLinks(scmPathInfoStore.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PermissionsLinks {
|
||||||
|
private final LinkBuilder permissionsLlinkBuilder;
|
||||||
|
|
||||||
|
PermissionsLinks(ScmPathInfo scmPathInfo) {
|
||||||
|
this.permissionsLlinkBuilder = new LinkBuilder(scmPathInfo, GlobalPermissionResource.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
String self() {
|
||||||
|
return permissionsLlinkBuilder.method("getAll").parameters().href();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
|
import sonia.scm.security.PermissionAssigner;
|
||||||
|
import sonia.scm.security.PermissionDescriptor;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.PUT;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class UserPermissionResource {
|
||||||
|
|
||||||
|
private final PermissionAssigner permissionAssigner;
|
||||||
|
private final PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public UserPermissionResource(PermissionAssigner permissionAssigner, PermissionCollectionToDtoMapper permissionCollectionToDtoMapper) {
|
||||||
|
this.permissionAssigner = permissionAssigner;
|
||||||
|
this.permissionCollectionToDtoMapper = permissionCollectionToDtoMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns permissions for a user.
|
||||||
|
*
|
||||||
|
* @param id the id/name of the user
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("")
|
||||||
|
@Produces(VndMediaType.PERMISSION_COLLECTION)
|
||||||
|
@TypeHint(PermissionListDto.class)
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the user"),
|
||||||
|
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
public Response getPermissions(@PathParam("id") String id) {
|
||||||
|
Collection<PermissionDescriptor> permissions = permissionAssigner.readPermissionsForUser(id);
|
||||||
|
return Response.ok(permissionCollectionToDtoMapper.mapForUser(permissions, id)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets permissions for a user. Overwrites all existing permissions.
|
||||||
|
*
|
||||||
|
* @param id id of the user to be modified
|
||||||
|
* @param newPermissions New list of permissions for the user
|
||||||
|
*/
|
||||||
|
@PUT
|
||||||
|
@Path("")
|
||||||
|
@Consumes(VndMediaType.PERMISSION_COLLECTION)
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 204, condition = "update success"),
|
||||||
|
@ResponseCode(code = 400, condition = "Invalid body"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the correct privilege"),
|
||||||
|
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
|
public Response overwritePermissions(@PathParam("id") String id, PermissionListDto newPermissions) {
|
||||||
|
Collection<PermissionDescriptor> permissionDescriptors = Arrays.stream(newPermissions.getPermissions())
|
||||||
|
.map(PermissionDescriptor::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
permissionAssigner.setPermissionsForUser(id, permissionDescriptors);
|
||||||
|
return Response.noContent().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ import sonia.scm.user.UserManager;
|
|||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
@@ -28,14 +27,20 @@ public class UserResource {
|
|||||||
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
||||||
private final UserManager userManager;
|
private final UserManager userManager;
|
||||||
private final PasswordService passwordService;
|
private final PasswordService passwordService;
|
||||||
|
private final UserPermissionResource userPermissionResource;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UserResource(UserDtoToUserMapper dtoToUserMapper, UserToUserDtoMapper userToDtoMapper, UserManager manager, PasswordService passwordService) {
|
public UserResource(
|
||||||
|
UserDtoToUserMapper dtoToUserMapper,
|
||||||
|
UserToUserDtoMapper userToDtoMapper,
|
||||||
|
UserManager manager,
|
||||||
|
PasswordService passwordService, UserPermissionResource userPermissionResource) {
|
||||||
this.dtoToUserMapper = dtoToUserMapper;
|
this.dtoToUserMapper = dtoToUserMapper;
|
||||||
this.userToDtoMapper = userToDtoMapper;
|
this.userToDtoMapper = userToDtoMapper;
|
||||||
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
||||||
this.userManager = manager;
|
this.userManager = manager;
|
||||||
this.passwordService = passwordService;
|
this.passwordService = passwordService;
|
||||||
|
this.userPermissionResource = userPermissionResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,4 +137,9 @@ public class UserResource {
|
|||||||
userManager.overwritePassword(name, passwordService.encryptPassword(passwordOverwrite.getNewPassword()));
|
userManager.overwritePassword(name, passwordService.encryptPassword(passwordOverwrite.getNewPassword()));
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("permissions")
|
||||||
|
public UserPermissionResource permissions() {
|
||||||
|
return userPermissionResource;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.mapstruct.AfterMapping;
|
|||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
|
import sonia.scm.security.PermissionPermissions;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.user.UserPermissions;
|
import sonia.scm.user.UserPermissions;
|
||||||
@@ -42,6 +43,9 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
|||||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
|
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (PermissionPermissions.read().isPermitted()) {
|
||||||
|
linksBuilder.single(link("permissions", resourceLinks.userPermissions().permissions(target.getName())));
|
||||||
|
}
|
||||||
|
|
||||||
appendLinks(new EdisonLinkAppender(linksBuilder), user);
|
appendLinks(new EdisonLinkAppender(linksBuilder), user);
|
||||||
|
|
||||||
|
|||||||
@@ -189,9 +189,9 @@ public class AuthorizationChangedEventProducer {
|
|||||||
* @param event permission event
|
* @param event permission event
|
||||||
*/
|
*/
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onEvent(StoredAssignedPermissionEvent event) {
|
public void onEvent(AssignedPermissionEvent event) {
|
||||||
if (event.getEventType().isPost()) {
|
if (event.getEventType().isPost()) {
|
||||||
StoredAssignedPermission permission = event.getPermission();
|
AssignedPermission permission = event.getPermission();
|
||||||
if (permission.isGroupPermission()) {
|
if (permission.isGroupPermission()) {
|
||||||
handleGroupPermissionChange(permission);
|
handleGroupPermissionChange(permission);
|
||||||
} else {
|
} else {
|
||||||
@@ -200,18 +200,18 @@ public class AuthorizationChangedEventProducer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleGroupPermissionChange(StoredAssignedPermission permission) {
|
private void handleGroupPermissionChange(AssignedPermission permission) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"fire authorization changed event, because global group permission {} has changed",
|
"fire authorization changed event for group {}, because permission {} has changed",
|
||||||
permission.getId()
|
permission.getName(), permission.getPermission()
|
||||||
);
|
);
|
||||||
fireEventForEveryUser();
|
fireEventForEveryUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUserPermissionChange(StoredAssignedPermission permission) {
|
private void handleUserPermissionChange(AssignedPermission permission) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"fire authorization changed event for user {}, because permission {} has changed",
|
"fire authorization changed event for user {}, because permission {} has changed",
|
||||||
permission.getName(), permission.getId()
|
permission.getName(), permission.getPermission()
|
||||||
);
|
);
|
||||||
fireEventForUser(permission.getName());
|
fireEventForUser(permission.getName());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,11 +101,11 @@ public class BearerRealm extends AuthenticatingRealm
|
|||||||
BearerToken bt = (BearerToken) token;
|
BearerToken bt = (BearerToken) token;
|
||||||
AccessToken accessToken = tokenResolver.resolve(bt);
|
AccessToken accessToken = tokenResolver.resolve(bt);
|
||||||
|
|
||||||
return helper.getAuthenticationInfo(
|
return helper.authenticationInfoBuilder(accessToken.getSubject())
|
||||||
accessToken.getSubject(),
|
.withCredentials(bt.getCredentials())
|
||||||
bt.getCredentials(),
|
.withScope(Scopes.fromClaims(accessToken.getClaims()))
|
||||||
Scopes.fromClaims(accessToken.getClaims())
|
.withGroups(accessToken.getGroups())
|
||||||
);
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,11 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.cache.Cache;
|
import sonia.scm.cache.Cache;
|
||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.group.GroupNames;
|
import sonia.scm.group.GroupNames;
|
||||||
import sonia.scm.group.GroupPermissions;
|
import sonia.scm.group.GroupPermissions;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.repository.Permission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryDAO;
|
import sonia.scm.repository.RepositoryDAO;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
@@ -62,7 +63,6 @@ import sonia.scm.user.UserPermissions;
|
|||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -76,9 +76,6 @@ import java.util.Set;
|
|||||||
public class DefaultAuthorizationCollector implements AuthorizationCollector
|
public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||||
{
|
{
|
||||||
|
|
||||||
// TODO move to util class
|
|
||||||
private static final String SEPARATOR = System.getProperty("line.separator", "\n");
|
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final String ADMIN_PERMISSION = "*";
|
private static final String ADMIN_PERMISSION = "*";
|
||||||
|
|
||||||
@@ -98,14 +95,16 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
|
* @param configuration
|
||||||
* @param cacheManager
|
* @param cacheManager
|
||||||
* @param repositoryDAO
|
* @param repositoryDAO
|
||||||
* @param securitySystem
|
* @param securitySystem
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public DefaultAuthorizationCollector(CacheManager cacheManager,
|
public DefaultAuthorizationCollector(ScmConfiguration configuration, CacheManager cacheManager,
|
||||||
RepositoryDAO repositoryDAO, SecuritySystem securitySystem)
|
RepositoryDAO repositoryDAO, SecuritySystem securitySystem)
|
||||||
{
|
{
|
||||||
|
this.configuration = configuration;
|
||||||
this.cache = cacheManager.getCache(CACHE_NAME);
|
this.cache = cacheManager.getCache(CACHE_NAME);
|
||||||
this.repositoryDAO = repositoryDAO;
|
this.repositoryDAO = repositoryDAO;
|
||||||
this.securitySystem = securitySystem;
|
this.securitySystem = securitySystem;
|
||||||
@@ -175,12 +174,12 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
|||||||
private void collectGlobalPermissions(Builder<String> builder,
|
private void collectGlobalPermissions(Builder<String> builder,
|
||||||
final User user, final GroupNames groups)
|
final User user, final GroupNames groups)
|
||||||
{
|
{
|
||||||
List<StoredAssignedPermission> globalPermissions =
|
Collection<AssignedPermission> globalPermissions =
|
||||||
securitySystem.getPermissions((AssignedPermission input) -> isUserPermitted(user, groups, input));
|
securitySystem.getPermissions((AssignedPermission input) -> isUserPermitted(user, groups, input));
|
||||||
|
|
||||||
for (StoredAssignedPermission gp : globalPermissions)
|
for (AssignedPermission gp : globalPermissions)
|
||||||
{
|
{
|
||||||
String permission = gp.getPermission();
|
String permission = gp.getPermission().getValue();
|
||||||
|
|
||||||
logger.trace("add permission {} for user {}", permission, user.getName());
|
logger.trace("add permission {} for user {}", permission, user.getName());
|
||||||
builder.add(permission);
|
builder.add(permission);
|
||||||
@@ -199,13 +198,13 @@ 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<Permission> repositoryPermissions
|
Collection<RepositoryPermission> repositoryPermissions
|
||||||
= repository.getPermissions();
|
= repository.getPermissions();
|
||||||
|
|
||||||
if (Util.isNotEmpty(repositoryPermissions))
|
if (Util.isNotEmpty(repositoryPermissions))
|
||||||
{
|
{
|
||||||
boolean hasPermission = false;
|
boolean hasPermission = false;
|
||||||
for (sonia.scm.repository.Permission permission : repositoryPermissions)
|
for (RepositoryPermission permission : repositoryPermissions)
|
||||||
{
|
{
|
||||||
hasPermission = isUserPermitted(user, groups, permission);
|
hasPermission = isUserPermitted(user, groups, permission);
|
||||||
if (hasPermission)
|
if (hasPermission)
|
||||||
@@ -239,7 +238,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
|||||||
Set<String> roles;
|
Set<String> roles;
|
||||||
Set<String> permissions;
|
Set<String> permissions;
|
||||||
|
|
||||||
if (user.isAdmin())
|
if (isAdmin(user, groups))
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
@@ -270,6 +269,37 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAdmin(User user, GroupNames groups) {
|
||||||
|
boolean admin = user.isAdmin();
|
||||||
|
if (admin) {
|
||||||
|
logger.debug("user {} is marked as admin, because of the user flag", user.getName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isUserAdminInConfiguration(user)) {
|
||||||
|
logger.debug("user {} is marked as admin, because of the admin user configuration", user.getName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isUserAdminInGroupConfiguration(user, groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUserAdminInGroupConfiguration(User user, GroupNames groups) {
|
||||||
|
Set<String> adminGroups = configuration.getAdminGroups();
|
||||||
|
if (adminGroups != null && groups != null) {
|
||||||
|
for (String group : groups) {
|
||||||
|
if (adminGroups.contains(group)) {
|
||||||
|
logger.debug("user {} is marked as admin, because of the admin group configuration for group {}", user.getName(), group);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUserAdminInConfiguration(User user) {
|
||||||
|
Set<String> adminUsers = configuration.getAdminUsers();
|
||||||
|
return adminUsers != null && adminUsers.contains(user.getName());
|
||||||
|
}
|
||||||
|
|
||||||
private String getGroupAutocompletePermission() {
|
private String getGroupAutocompletePermission() {
|
||||||
return GroupPermissions.autocomplete().asShiroString();
|
return GroupPermissions.autocomplete().asShiroString();
|
||||||
}
|
}
|
||||||
@@ -373,6 +403,8 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
|||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
|
private final ScmConfiguration configuration;
|
||||||
|
|
||||||
/** authorization cache */
|
/** authorization cache */
|
||||||
private final Cache<CacheKey, AuthorizationInfo> cache;
|
private final Cache<CacheKey, AuthorizationInfo> cache;
|
||||||
|
|
||||||
|
|||||||
@@ -36,38 +36,22 @@ package sonia.scm.security;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.github.legman.Subscribe;
|
import com.github.legman.Subscribe;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.ImmutableList.Builder;
|
import com.google.common.collect.ImmutableSet.Builder;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sonia.scm.HandlerEventType;
|
import sonia.scm.HandlerEventType;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.group.GroupEvent;
|
import sonia.scm.group.GroupEvent;
|
||||||
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.store.ConfigurationEntryStore;
|
import sonia.scm.store.ConfigurationEntryStore;
|
||||||
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
import sonia.scm.store.ConfigurationEntryStoreFactory;
|
||||||
import sonia.scm.user.UserEvent;
|
import sonia.scm.user.UserEvent;
|
||||||
import sonia.scm.util.ClassLoaders;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import javax.xml.bind.JAXBContext;
|
import javax.xml.bind.JAXBContext;
|
||||||
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.JAXBException;
|
||||||
@@ -75,6 +59,19 @@ 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.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.util.Objects.isNull;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO add events
|
* TODO add events
|
||||||
@@ -109,13 +106,13 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public DefaultSecuritySystem(ConfigurationEntryStoreFactory storeFactory)
|
public DefaultSecuritySystem(ConfigurationEntryStoreFactory storeFactory, PluginLoader pluginLoader)
|
||||||
{
|
{
|
||||||
store = storeFactory
|
store = storeFactory
|
||||||
.withType(AssignedPermission.class)
|
.withType(AssignedPermission.class)
|
||||||
.withName(NAME)
|
.withName(NAME)
|
||||||
.build();
|
.build();
|
||||||
readAvailablePermissions();
|
this.availablePermissions = readAvailablePermissions(pluginLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -129,9 +126,9 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public StoredAssignedPermission addPermission(AssignedPermission permission)
|
public void addPermission(AssignedPermission permission)
|
||||||
{
|
{
|
||||||
assertIsAdmin();
|
assertHasPermission();
|
||||||
validatePermission(permission);
|
validatePermission(permission);
|
||||||
|
|
||||||
String id = store.put(permission);
|
String id = store.put(permission);
|
||||||
@@ -140,11 +137,9 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
|
|
||||||
//J-
|
//J-
|
||||||
ScmEventBus.getInstance().post(
|
ScmEventBus.getInstance().post(
|
||||||
new StoredAssignedPermissionEvent(HandlerEventType.CREATE, sap)
|
new AssignedPermissionEvent(HandlerEventType.CREATE, permission)
|
||||||
);
|
);
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
return sap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,33 +149,16 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
* @param permission
|
* @param permission
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void deletePermission(StoredAssignedPermission permission)
|
public void deletePermission(AssignedPermission permission)
|
||||||
{
|
{
|
||||||
assertIsAdmin();
|
assertHasPermission();
|
||||||
store.remove(permission.getId());
|
boolean deleted = deletePermissions(sap -> Objects.equal(sap.getName(), permission.getName())
|
||||||
//J-
|
&& Objects.equal(sap.isGroupPermission(), permission.isGroupPermission())
|
||||||
ScmEventBus.getInstance().post(
|
&& Objects.equal(sap.getPermission(), permission.getPermission()));
|
||||||
new StoredAssignedPermissionEvent(HandlerEventType.CREATE, permission)
|
if (deleted) {
|
||||||
);
|
ScmEventBus.getInstance().post(
|
||||||
//J+
|
new AssignedPermissionEvent(HandlerEventType.DELETE, permission)
|
||||||
}
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void deletePermission(String id)
|
|
||||||
{
|
|
||||||
assertIsAdmin();
|
|
||||||
|
|
||||||
AssignedPermission ap = store.get(id);
|
|
||||||
|
|
||||||
if (ap != null)
|
|
||||||
{
|
|
||||||
deletePermission(new StoredAssignedPermission(id, ap));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,16 +173,8 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
{
|
{
|
||||||
if (event.getEventType() == HandlerEventType.DELETE)
|
if (event.getEventType() == HandlerEventType.DELETE)
|
||||||
{
|
{
|
||||||
deletePermissions(new Predicate<AssignedPermission>()
|
deletePermissions(p -> !p.isGroupPermission()
|
||||||
{
|
&& event.getItem().getName().equals(p.getName()));
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean apply(AssignedPermission p)
|
|
||||||
{
|
|
||||||
return !p.isGroupPermission()
|
|
||||||
&& event.getItem().getName().equals(p.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,44 +189,11 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
{
|
{
|
||||||
if (event.getEventType() == HandlerEventType.DELETE)
|
if (event.getEventType() == HandlerEventType.DELETE)
|
||||||
{
|
{
|
||||||
deletePermissions(new Predicate<AssignedPermission>()
|
deletePermissions(p -> p.isGroupPermission()
|
||||||
{
|
&& event.getItem().getName().equals(p.getName()));
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean apply(AssignedPermission p)
|
|
||||||
{
|
|
||||||
return p.isGroupPermission()
|
|
||||||
&& event.getItem().getName().equals(p.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param permission
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void modifyPermission(StoredAssignedPermission permission)
|
|
||||||
{
|
|
||||||
assertIsAdmin();
|
|
||||||
validatePermission(permission);
|
|
||||||
|
|
||||||
synchronized (store)
|
|
||||||
{
|
|
||||||
store.remove(permission.getId());
|
|
||||||
store.put(permission.getId(), new AssignedPermission(permission));
|
|
||||||
}
|
|
||||||
|
|
||||||
//J-
|
|
||||||
ScmEventBus.getInstance().post(
|
|
||||||
new StoredAssignedPermissionEvent(HandlerEventType.CREATE, permission)
|
|
||||||
);
|
|
||||||
//J+
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -266,49 +203,13 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<StoredAssignedPermission> getAllPermissions()
|
public Collection<PermissionDescriptor> getAvailablePermissions()
|
||||||
{
|
{
|
||||||
return getPermissions(null);
|
assertHasPermission();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<PermissionDescriptor> getAvailablePermissions()
|
|
||||||
{
|
|
||||||
assertIsAdmin();
|
|
||||||
|
|
||||||
return availablePermissions;
|
return availablePermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public StoredAssignedPermission getPermission(String id)
|
|
||||||
{
|
|
||||||
assertIsAdmin();
|
|
||||||
|
|
||||||
StoredAssignedPermission sap = null;
|
|
||||||
AssignedPermission ap = store.get(id);
|
|
||||||
|
|
||||||
if (ap != null)
|
|
||||||
{
|
|
||||||
sap = new StoredAssignedPermission(id, ap);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -318,14 +219,13 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<StoredAssignedPermission> getPermissions(
|
public Collection<AssignedPermission> getPermissions(Predicate<AssignedPermission> predicate)
|
||||||
Predicate<AssignedPermission> predicate)
|
|
||||||
{
|
{
|
||||||
Builder<StoredAssignedPermission> permissions = ImmutableList.builder();
|
Builder<AssignedPermission> permissions = ImmutableSet.builder();
|
||||||
|
|
||||||
for (Entry<String, AssignedPermission> e : store.getAll().entrySet())
|
for (Entry<String, AssignedPermission> e : store.getAll().entrySet())
|
||||||
{
|
{
|
||||||
if ((predicate == null) || predicate.apply(e.getValue()))
|
if ((predicate == null) || predicate.test(e.getValue()))
|
||||||
{
|
{
|
||||||
permissions.add(new StoredAssignedPermission(e.getKey(), e.getValue()));
|
permissions.add(new StoredAssignedPermission(e.getKey(), e.getValue()));
|
||||||
}
|
}
|
||||||
@@ -340,9 +240,9 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private void assertIsAdmin()
|
private void assertHasPermission()
|
||||||
{
|
{
|
||||||
SecurityUtils.getSubject().checkRole(Role.ADMIN);
|
PermissionPermissions.assign().check();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -351,14 +251,15 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
*
|
*
|
||||||
* @param predicate
|
* @param predicate
|
||||||
*/
|
*/
|
||||||
private void deletePermissions(Predicate<AssignedPermission> predicate)
|
private boolean deletePermissions(Predicate<AssignedPermission> predicate)
|
||||||
{
|
{
|
||||||
List<StoredAssignedPermission> permissions = getPermissions(predicate);
|
List<Entry<String, AssignedPermission>> toRemove =
|
||||||
|
store.getAll()
|
||||||
for (StoredAssignedPermission permission : permissions)
|
.entrySet()
|
||||||
{
|
.stream()
|
||||||
deletePermission(permission);
|
.filter(e -> (predicate == null) || predicate.test(e.getValue())).collect(Collectors.toList());
|
||||||
}
|
toRemove.forEach(e -> store.remove(e.getKey()));
|
||||||
|
return !toRemove.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -371,7 +272,7 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private List<PermissionDescriptor> parsePermissionDescriptor(
|
private static List<PermissionDescriptor> parsePermissionDescriptor(
|
||||||
JAXBContext context, URL descriptorUrl)
|
JAXBContext context, URL descriptorUrl)
|
||||||
{
|
{
|
||||||
List<PermissionDescriptor> descriptors = Collections.EMPTY_LIST;
|
List<PermissionDescriptor> descriptors = Collections.EMPTY_LIST;
|
||||||
@@ -399,19 +300,20 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
* @param pluginLoader
|
||||||
*/
|
*/
|
||||||
private void readAvailablePermissions()
|
private static ImmutableSet<PermissionDescriptor> readAvailablePermissions(PluginLoader pluginLoader)
|
||||||
{
|
{
|
||||||
Builder<PermissionDescriptor> builder = ImmutableList.builder();
|
ImmutableSet.Builder<PermissionDescriptor> builder = ImmutableSet.builder();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
JAXBContext context =
|
JAXBContext context =
|
||||||
JAXBContext.newInstance(PermissionDescriptors.class);
|
JAXBContext.newInstance(PermissionDescriptors.class);
|
||||||
|
|
||||||
|
// Querying permissions from uberClassLoader returns also the permissions from plugin
|
||||||
Enumeration<URL> descirptorEnum =
|
Enumeration<URL> descirptorEnum =
|
||||||
ClassLoaders.getContextClassLoader(
|
pluginLoader.getUberClassLoader().getResources(PERMISSION_DESCRIPTOR);
|
||||||
DefaultSecuritySystem.class).getResources(PERMISSION_DESCRIPTOR);
|
|
||||||
|
|
||||||
while (descirptorEnum.hasMoreElements())
|
while (descirptorEnum.hasMoreElements())
|
||||||
{
|
{
|
||||||
@@ -432,7 +334,7 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
"could not create jaxb context to read permission descriptors", ex);
|
"could not create jaxb context to read permission descriptors", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
availablePermissions = builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -445,7 +347,7 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
{
|
{
|
||||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(perm.getName()),
|
Preconditions.checkArgument(!Strings.isNullOrEmpty(perm.getName()),
|
||||||
"name is required");
|
"name is required");
|
||||||
Preconditions.checkArgument(!Strings.isNullOrEmpty(perm.getPermission()),
|
Preconditions.checkArgument(!isNull(perm.getPermission()),
|
||||||
"permission is required");
|
"permission is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,12 +361,6 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
private static class PermissionDescriptors
|
private static class PermissionDescriptors
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public PermissionDescriptors() {}
|
|
||||||
|
|
||||||
//~--- get methods --------------------------------------------------------
|
//~--- get methods --------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -498,5 +394,5 @@ public class DefaultSecuritySystem implements SecuritySystem
|
|||||||
private final ConfigurationEntryStore<AssignedPermission> store;
|
private final ConfigurationEntryStore<AssignedPermission> store;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private List<PermissionDescriptor> availablePermissions;
|
private final ImmutableSet<PermissionDescriptor> availablePermissions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,15 @@
|
|||||||
*/
|
*/
|
||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
|
|
||||||
@@ -49,6 +52,8 @@ public final class JwtAccessToken implements AccessToken {
|
|||||||
|
|
||||||
public static final String REFRESHABLE_UNTIL_CLAIM_KEY = "scm-manager.refreshExpiration";
|
public static final String REFRESHABLE_UNTIL_CLAIM_KEY = "scm-manager.refreshExpiration";
|
||||||
public static final String PARENT_TOKEN_ID_CLAIM_KEY = "scm-manager.parentTokenId";
|
public static final String PARENT_TOKEN_ID_CLAIM_KEY = "scm-manager.parentTokenId";
|
||||||
|
public static final String GROUPS_CLAIM_KEY = "scm-manager.groups";
|
||||||
|
|
||||||
private final Claims claims;
|
private final Claims claims;
|
||||||
private final String compact;
|
private final String compact;
|
||||||
|
|
||||||
@@ -103,6 +108,16 @@ public final class JwtAccessToken implements AccessToken {
|
|||||||
return Optional.ofNullable(claims.get(key));
|
return Optional.ofNullable(claims.get(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Set<String> getGroups() {
|
||||||
|
Iterable<String> groups = claims.get(GROUPS_CLAIM_KEY, Iterable.class);
|
||||||
|
if (groups != null) {
|
||||||
|
return ImmutableSet.copyOf(groups);
|
||||||
|
}
|
||||||
|
return ImmutableSet.of();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String compact() {
|
public String compact() {
|
||||||
return compact;
|
return compact;
|
||||||
|
|||||||
@@ -39,9 +39,12 @@ import io.jsonwebtoken.SignatureAlgorithm;
|
|||||||
|
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
@@ -74,6 +77,7 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
|||||||
private Instant refreshExpiration;
|
private Instant refreshExpiration;
|
||||||
private String parentKeyId;
|
private String parentKeyId;
|
||||||
private Scope scope = Scope.empty();
|
private Scope scope = Scope.empty();
|
||||||
|
private Set<String> groups = new HashSet<>();
|
||||||
|
|
||||||
private final Map<String,Object> custom = Maps.newHashMap();
|
private final Map<String,Object> custom = Maps.newHashMap();
|
||||||
|
|
||||||
@@ -134,6 +138,12 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JwtAccessTokenBuilder groups(String... groups) {
|
||||||
|
Collections.addAll(this.groups, groups);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
JwtAccessTokenBuilder refreshExpiration(Instant refreshExpiration) {
|
JwtAccessTokenBuilder refreshExpiration(Instant refreshExpiration) {
|
||||||
this.refreshExpiration = refreshExpiration;
|
this.refreshExpiration = refreshExpiration;
|
||||||
this.refreshableFor = 0;
|
this.refreshableFor = 0;
|
||||||
@@ -195,6 +205,10 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
|||||||
claims.setIssuer(issuer);
|
claims.setIssuer(issuer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!groups.isEmpty()) {
|
||||||
|
claims.put(JwtAccessToken.GROUPS_CLAIM_KEY, groups);
|
||||||
|
}
|
||||||
|
|
||||||
// sign token and create compact version
|
// sign token and create compact version
|
||||||
String compact = Jwts.builder()
|
String compact = Jwts.builder()
|
||||||
.setClaims(claims)
|
.setClaims(claims)
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import sonia.scm.ContextEntry;
|
||||||
|
import sonia.scm.NotFoundException;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class PermissionAssigner {
|
||||||
|
|
||||||
|
private final SecuritySystem securitySystem;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PermissionAssigner(SecuritySystem securitySystem) {
|
||||||
|
this.securitySystem = securitySystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<PermissionDescriptor> getAvailablePermissions() {
|
||||||
|
PermissionPermissions.read().check();
|
||||||
|
return securitySystem.getAvailablePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<PermissionDescriptor> readPermissionsForUser(String id) {
|
||||||
|
return readPermissions(filterForUser(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<PermissionDescriptor> readPermissionsForGroup(String id) {
|
||||||
|
return readPermissions(filterForGroup(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Predicate<AssignedPermission> filterForUser(String id) {
|
||||||
|
return p -> !p.isGroupPermission() && p.getName().equals(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Predicate<AssignedPermission> filterForGroup(String id) {
|
||||||
|
return p -> p.isGroupPermission() && p.getName().equals(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<PermissionDescriptor> readPermissions(Predicate<AssignedPermission> predicate) {
|
||||||
|
PermissionPermissions.read().check();
|
||||||
|
return securitySystem.getPermissions(predicate)
|
||||||
|
.stream()
|
||||||
|
.map(AssignedPermission::getPermission)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermissionsForUser(String id, Collection<PermissionDescriptor> permissions) {
|
||||||
|
Collection<AssignedPermission> existingPermissions = securitySystem.getPermissions(filterForUser(id));
|
||||||
|
adaptPermissions(id, false, permissions, existingPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermissionsForGroup(String id, Collection<PermissionDescriptor> permissions) {
|
||||||
|
Collection<AssignedPermission> existingPermissions = securitySystem.getPermissions(filterForGroup(id));
|
||||||
|
adaptPermissions(id, true, permissions, existingPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adaptPermissions(String id, boolean groupPermission, Collection<PermissionDescriptor> permissions, Collection<AssignedPermission> existingPermissions) {
|
||||||
|
PermissionPermissions.assign().check();
|
||||||
|
List<AssignedPermission> toRemove = existingPermissions.stream()
|
||||||
|
.filter(p -> !permissions.contains(p.getPermission()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
toRemove.forEach(securitySystem::deletePermission);
|
||||||
|
|
||||||
|
Collection<PermissionDescriptor> availablePermissions = this.getAvailablePermissions();
|
||||||
|
|
||||||
|
permissions.stream()
|
||||||
|
.filter(permissionExists(availablePermissions, existingPermissions))
|
||||||
|
.map(p -> new AssignedPermission(id, groupPermission, p))
|
||||||
|
.filter(p -> !existingPermissions.contains(p))
|
||||||
|
.forEach(securitySystem::addPermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Predicate<PermissionDescriptor> permissionExists(Collection<PermissionDescriptor> availablePermissions, Collection<AssignedPermission> existingPermissions) {
|
||||||
|
return p -> {
|
||||||
|
if (!availablePermissions.contains(p) && existingPermissions.stream().map(AssignedPermission::getPermission).noneMatch(e -> e.equals(p))) {
|
||||||
|
throw NotFoundException.notFound(ContextEntry.ContextBuilder.entity("permission", p.getValue()));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,21 +34,22 @@
|
|||||||
<permissions>
|
<permissions>
|
||||||
|
|
||||||
<permission>
|
<permission>
|
||||||
<display-name>All Repository (read)</display-name>
|
<value>repository:read,pull:*</value>
|
||||||
<description>Read access to all repositories</description>
|
|
||||||
<value>repository:*:READ</value>
|
|
||||||
</permission>
|
</permission>
|
||||||
|
|
||||||
<permission>
|
<permission>
|
||||||
<display-name>All Repository (write)</display-name>
|
<value>repository:read,pull,push:*</value>
|
||||||
<description>Write access to all repositories</description>
|
|
||||||
<value>repository:*:WRITE</value>
|
|
||||||
</permission>
|
</permission>
|
||||||
|
|
||||||
<permission>
|
<permission>
|
||||||
<display-name>All Repository (owner)</display-name>
|
<value>repository:*:*</value>
|
||||||
<description>Owner access to all repositories</description>
|
|
||||||
<value>repository:*:OWNER</value>
|
|
||||||
</permission>
|
</permission>
|
||||||
|
<permission>
|
||||||
|
<value>repository:create</value>
|
||||||
|
</permission>
|
||||||
|
<permission>
|
||||||
|
<value>user:*</value>
|
||||||
|
</permission>
|
||||||
|
<permission>
|
||||||
|
<value>group:*</value>
|
||||||
|
</permission>
|
||||||
|
|
||||||
</permissions>
|
</permissions>
|
||||||
|
|||||||
41
scm-webapp/src/main/resources/locales/en/plugins.json
Normal file
41
scm-webapp/src/main/resources/locales/en/plugins.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"repository": {
|
||||||
|
"read,pull": {
|
||||||
|
"*": {
|
||||||
|
"displayName": "Read all repositories",
|
||||||
|
"description": "May see and clone all repositories"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"read,pull,push": {
|
||||||
|
"*": {
|
||||||
|
"displayName": "Write all repositories",
|
||||||
|
"description": "May see, clone and push to all repositories"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"*": {
|
||||||
|
"*": {
|
||||||
|
"displayName": "Own all repositories",
|
||||||
|
"description": "May see, clone, push to, configure and delete all repositories"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"displayName": "Create repositories",
|
||||||
|
"description": "May create repositories"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"*": {
|
||||||
|
"displayName": "Administer users",
|
||||||
|
"description": "May administer all users"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"*": {
|
||||||
|
"displayName": "Administer groups",
|
||||||
|
"description": "May administer all groups"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unknown": "Unknown permission"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ import sonia.scm.api.rest.JSONContextResolver;
|
|||||||
import sonia.scm.api.rest.ObjectMapperProvider;
|
import sonia.scm.api.rest.ObjectMapperProvider;
|
||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
import sonia.scm.group.GroupManager;
|
import sonia.scm.group.GroupManager;
|
||||||
|
import sonia.scm.security.PermissionAssigner;
|
||||||
|
import sonia.scm.security.PermissionDescriptor;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@@ -25,6 +27,7 @@ import java.io.IOException;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
@@ -54,10 +57,15 @@ public class GroupRootResourceTest {
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private GroupManager groupManager;
|
private GroupManager groupManager;
|
||||||
|
@Mock
|
||||||
|
private PermissionAssigner permissionAssigner;
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private GroupDtoToGroupMapperImpl dtoToGroupMapper;
|
private GroupDtoToGroupMapperImpl dtoToGroupMapper;
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private GroupToGroupDtoMapperImpl groupToDtoMapper;
|
private GroupToGroupDtoMapperImpl groupToDtoMapper;
|
||||||
|
@InjectMocks
|
||||||
|
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
||||||
|
|
||||||
|
|
||||||
private ArgumentCaptor<Group> groupCaptor = ArgumentCaptor.forClass(Group.class);
|
private ArgumentCaptor<Group> groupCaptor = ArgumentCaptor.forClass(Group.class);
|
||||||
|
|
||||||
@@ -73,7 +81,8 @@ public class GroupRootResourceTest {
|
|||||||
|
|
||||||
GroupCollectionToDtoMapper groupCollectionToDtoMapper = new GroupCollectionToDtoMapper(groupToDtoMapper, resourceLinks);
|
GroupCollectionToDtoMapper groupCollectionToDtoMapper = new GroupCollectionToDtoMapper(groupToDtoMapper, resourceLinks);
|
||||||
GroupCollectionResource groupCollectionResource = new GroupCollectionResource(groupManager, dtoToGroupMapper, groupCollectionToDtoMapper, resourceLinks);
|
GroupCollectionResource groupCollectionResource = new GroupCollectionResource(groupManager, dtoToGroupMapper, groupCollectionToDtoMapper, resourceLinks);
|
||||||
GroupResource groupResource = new GroupResource(groupManager, groupToDtoMapper, dtoToGroupMapper);
|
GroupPermissionResource groupPermissionResource = new GroupPermissionResource(permissionAssigner, permissionCollectionToDtoMapper);
|
||||||
|
GroupResource groupResource = new GroupResource(groupManager, groupToDtoMapper, dtoToGroupMapper, groupPermissionResource);
|
||||||
GroupRootResource groupRootResource = new GroupRootResource(Providers.of(groupCollectionResource), Providers.of(groupResource));
|
GroupRootResource groupRootResource = new GroupRootResource(Providers.of(groupCollectionResource), Providers.of(groupResource));
|
||||||
|
|
||||||
dispatcher = createDispatcher(groupRootResource);
|
dispatcher = createDispatcher(groupRootResource);
|
||||||
@@ -307,6 +316,48 @@ public class GroupRootResourceTest {
|
|||||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/groups/admin\"}"));
|
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/groups/admin\"}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetPermissionLink() throws URISyntaxException {
|
||||||
|
MockHttpRequest request = MockHttpRequest.get("/" + GroupRootResource.GROUPS_PATH_V2 + "admin");
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
|
|
||||||
|
assertTrue(response.getContentAsString().contains("\"permissions\":{"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetPermissions() throws URISyntaxException {
|
||||||
|
when(permissionAssigner.readPermissionsForGroup("admin")).thenReturn(singletonList(new PermissionDescriptor("something:*")));
|
||||||
|
MockHttpRequest request = MockHttpRequest.get("/" + GroupRootResource.GROUPS_PATH_V2 + "admin/permissions");
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
|
|
||||||
|
assertTrue(response.getContentAsString().contains("\"permissions\":[\"something:*\"]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSetPermissions() throws URISyntaxException {
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.put("/" + GroupRootResource.GROUPS_PATH_V2 + "admin/permissions")
|
||||||
|
.contentType(VndMediaType.PERMISSION_COLLECTION)
|
||||||
|
.content("{\"permissions\":[\"other:*\"]}".getBytes());
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
ArgumentCaptor<Collection<PermissionDescriptor>> captor = ArgumentCaptor.forClass(Collection.class);
|
||||||
|
doNothing().when(permissionAssigner).setPermissionsForGroup(eq("admin"), captor.capture());
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||||
|
|
||||||
|
assertEquals("other:*", captor.getValue().iterator().next().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
private Group createDummyGroup() {
|
private Group createDummyGroup() {
|
||||||
Group group = new Group();
|
Group group = new Group();
|
||||||
group.setName("admin");
|
group.setName("admin");
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.assertj.core.util.Lists;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.mockito.junit.jupiter.MockitoSettings;
|
||||||
|
import org.mockito.quality.Strictness;
|
||||||
|
import sonia.scm.group.GroupNames;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.UserManager;
|
||||||
|
import sonia.scm.user.UserPermissions;
|
||||||
|
import sonia.scm.user.UserTestData;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||||
|
class MeDtoFactoryTest {
|
||||||
|
|
||||||
|
private final URI baseUri = URI.create("https://scm.hitchhiker.com/scm/");
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private UserManager userManager;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
private MeDtoFactory meDtoFactory;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpContext() {
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
|
meDtoFactory = new MeDtoFactory(resourceLinks, userManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void unbindSubject() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateMeDtoFromUser() {
|
||||||
|
prepareSubject(UserTestData.createTrillian());
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getName()).isEqualTo("trillian");
|
||||||
|
assertThat(dto.getDisplayName()).isEqualTo("Tricia McMillan");
|
||||||
|
assertThat(dto.getMail()).isEqualTo("tricia.mcmillan@hitchhiker.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateMeDtoWithEmptyGroups() {
|
||||||
|
prepareSubject(UserTestData.createTrillian());
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getGroups()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateMeDtoWithGroups() {
|
||||||
|
prepareSubject(UserTestData.createTrillian(), "HeartOfGold", "Puzzle42");
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getGroups()).containsOnly("HeartOfGold", "Puzzle42");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareSubject(User user, String... groups) {
|
||||||
|
PrincipalCollection collection = mock(PrincipalCollection.class);
|
||||||
|
when(subject.getPrincipals()).thenReturn(collection);
|
||||||
|
when(collection.oneByType(any(Class.class))).then(ic -> {
|
||||||
|
Class<?> type = ic.getArgument(0);
|
||||||
|
if (type.isAssignableFrom(User.class)) {
|
||||||
|
return user;
|
||||||
|
} else if (type.isAssignableFrom(GroupNames.class)) {
|
||||||
|
return new GroupNames(Lists.newArrayList(groups));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAppendSelfLink() {
|
||||||
|
prepareSubject(UserTestData.createTrillian());
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo("https://scm.hitchhiker.com/scm/v2/me/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAppendDeleteLink() {
|
||||||
|
prepareSubject(UserTestData.createTrillian());
|
||||||
|
when(subject.isPermitted("user:delete:trillian")).thenReturn(true);
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("delete").get().getHref()).isEqualTo("https://scm.hitchhiker.com/scm/v2/users/trillian");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotAppendDeleteLink() {
|
||||||
|
prepareSubject(UserTestData.createTrillian());
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("delete")).isNotPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAppendUpdateLink() {
|
||||||
|
prepareSubject(UserTestData.createTrillian());
|
||||||
|
when(subject.isPermitted("user:modify:trillian")).thenReturn(true);
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("update").get().getHref()).isEqualTo("https://scm.hitchhiker.com/scm/v2/users/trillian");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotAppendUpdateLink() {
|
||||||
|
prepareSubject(UserTestData.createTrillian());
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("update")).isNotPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetPasswordLinkOnlyForDefaultUserType() {
|
||||||
|
User user = UserTestData.createTrillian();
|
||||||
|
prepareSubject(user);
|
||||||
|
|
||||||
|
when(subject.isPermitted("user:changePassword:trillian")).thenReturn(true);
|
||||||
|
when(userManager.isTypeDefault(user)).thenReturn(true);
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("password").get().getHref()).isEqualTo("https://scm.hitchhiker.com/scm/v2/me/password");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotGetPasswordLinkWithoutPermision() {
|
||||||
|
User user = UserTestData.createTrillian();
|
||||||
|
prepareSubject(user);
|
||||||
|
|
||||||
|
when(userManager.isTypeDefault(user)).thenReturn(true);
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("password")).isNotPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotGetPasswordLinkForNonDefaultUsers() {
|
||||||
|
User user = UserTestData.createTrillian();
|
||||||
|
prepareSubject(user);
|
||||||
|
|
||||||
|
when(subject.isPermitted("user:changePassword:trillian")).thenReturn(true);
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("password")).isNotPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAppendLinks() {
|
||||||
|
prepareSubject(UserTestData.createTrillian());
|
||||||
|
|
||||||
|
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
||||||
|
meDtoFactory.setRegistry(registry);
|
||||||
|
|
||||||
|
registry.register(Me.class, (ctx, appender) -> {
|
||||||
|
User user = ctx.oneRequireByType(User.class);
|
||||||
|
appender.appendOne("profile", "http://hitchhiker.com/users/" + user.getName());
|
||||||
|
});
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
assertThat(dto.getLinks().getLinkBy("profile").get().getHref()).isEqualTo("http://hitchhiker.com/users/trillian");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,12 +2,14 @@ package sonia.scm.api.v2.resources;
|
|||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.authc.credential.PasswordService;
|
import org.apache.shiro.authc.credential.PasswordService;
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
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;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
@@ -19,7 +21,6 @@ import sonia.scm.user.User;
|
|||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.lang.model.util.Types;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
@@ -27,11 +28,7 @@ import java.net.URISyntaxException;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.Mockito.*;
|
||||||
import static org.mockito.Mockito.doNothing;
|
|
||||||
import static org.mockito.Mockito.doThrow;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
import static org.mockito.MockitoAnnotations.initMocks;
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ public class MeResourceTest {
|
|||||||
private UserManager userManager;
|
private UserManager userManager;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private MeToUserDtoMapperImpl userToDtoMapper;
|
private MeDtoFactory meDtoFactory;
|
||||||
|
|
||||||
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||||
|
|
||||||
@@ -66,7 +63,7 @@ public class MeResourceTest {
|
|||||||
private User originalUser;
|
private User originalUser;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() throws Exception {
|
public void prepareEnvironment() {
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
originalUser = createDummyUser("trillian");
|
originalUser = createDummyUser("trillian");
|
||||||
when(userManager.create(userCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
|
when(userManager.create(userCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
|
||||||
@@ -74,17 +71,18 @@ public class MeResourceTest {
|
|||||||
doNothing().when(userManager).delete(userCaptor.capture());
|
doNothing().when(userManager).delete(userCaptor.capture());
|
||||||
when(userManager.isTypeDefault(userCaptor.capture())).thenCallRealMethod();
|
when(userManager.isTypeDefault(userCaptor.capture())).thenCallRealMethod();
|
||||||
when(userManager.getDefaultType()).thenReturn("xml");
|
when(userManager.getDefaultType()).thenReturn("xml");
|
||||||
MeResource meResource = new MeResource(userToDtoMapper, userManager, passwordService);
|
MeResource meResource = new MeResource(meDtoFactory, userManager, passwordService);
|
||||||
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/"));
|
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/"));
|
||||||
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
||||||
dispatcher = createDispatcher(meResource);
|
dispatcher = createDispatcher(meResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
|
||||||
public void shouldReturnCurrentlyAuthenticatedUser() throws URISyntaxException {
|
public void shouldReturnCurrentlyAuthenticatedUser() throws URISyntaxException {
|
||||||
|
applyUserToSubject(originalUser);
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + MeResource.ME_PATH_V2);
|
MockHttpRequest request = MockHttpRequest.get("/" + MeResource.ME_PATH_V2);
|
||||||
request.accept(VndMediaType.USER);
|
request.accept(VndMediaType.ME);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
@@ -95,8 +93,17 @@ public class MeResourceTest {
|
|||||||
assertTrue(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/trillian\"}"));
|
assertTrue(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/trillian\"}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyUserToSubject(User user) {
|
||||||
|
// use spy here to keep applied permissions from ShiroRule
|
||||||
|
Subject subject = spy(SecurityUtils.getSubject());
|
||||||
|
PrincipalCollection collection = mock(PrincipalCollection.class);
|
||||||
|
when(collection.getPrimaryPrincipal()).thenReturn(user.getName());
|
||||||
|
when(subject.getPrincipals()).thenReturn(collection);
|
||||||
|
when(collection.oneByType(User.class)).thenReturn(user);
|
||||||
|
shiro.setSubject(subject);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
|
||||||
public void shouldEncryptPasswordBeforeChanging() throws Exception {
|
public void shouldEncryptPasswordBeforeChanging() throws Exception {
|
||||||
String newPassword = "pwd123";
|
String newPassword = "pwd123";
|
||||||
String encryptedNewPassword = "encrypted123";
|
String encryptedNewPassword = "encrypted123";
|
||||||
@@ -124,7 +131,6 @@ public class MeResourceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
|
||||||
public void shouldGet400OnMissingOldPassword() throws Exception {
|
public void shouldGet400OnMissingOldPassword() throws Exception {
|
||||||
originalUser.setType("not an xml type");
|
originalUser.setType("not an xml type");
|
||||||
String newPassword = "pwd123";
|
String newPassword = "pwd123";
|
||||||
@@ -141,7 +147,6 @@ public class MeResourceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
|
||||||
public void shouldGet400OnMissingEmptyPassword() throws Exception {
|
public void shouldGet400OnMissingEmptyPassword() throws Exception {
|
||||||
String newPassword = "pwd123";
|
String newPassword = "pwd123";
|
||||||
String oldPassword = "";
|
String oldPassword = "";
|
||||||
@@ -158,7 +163,6 @@ public class MeResourceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
|
||||||
public void shouldMapExceptionFromManager() throws Exception {
|
public void shouldMapExceptionFromManager() throws Exception {
|
||||||
String newPassword = "pwd123";
|
String newPassword = "pwd123";
|
||||||
String oldPassword = "secret";
|
String oldPassword = "secret";
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
|
||||||
|
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
import org.apache.shiro.subject.support.SubjectThreadState;
|
|
||||||
import org.apache.shiro.util.ThreadContext;
|
|
||||||
import org.apache.shiro.util.ThreadState;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import sonia.scm.user.User;
|
|
||||||
import sonia.scm.user.UserManager;
|
|
||||||
import sonia.scm.user.UserTestData;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
import static org.mockito.MockitoAnnotations.initMocks;
|
|
||||||
|
|
||||||
public class MeToUserDtoMapperTest {
|
|
||||||
|
|
||||||
private final URI baseUri = URI.create("http://example.com/base/");
|
|
||||||
@SuppressWarnings("unused") // Is injected
|
|
||||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private UserManager userManager;
|
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
private MeToUserDtoMapperImpl mapper;
|
|
||||||
|
|
||||||
private final Subject subject = mock(Subject.class);
|
|
||||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
|
||||||
|
|
||||||
private URI expectedBaseUri;
|
|
||||||
private URI expectedUserBaseUri;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void init() {
|
|
||||||
initMocks(this);
|
|
||||||
when(userManager.getDefaultType()).thenReturn("xml");
|
|
||||||
expectedBaseUri = baseUri.resolve(MeResource.ME_PATH_V2 + "/");
|
|
||||||
expectedUserBaseUri = baseUri.resolve(UserRootResource.USERS_PATH_V2 + "/");
|
|
||||||
subjectThreadState.bind();
|
|
||||||
ThreadContext.bind(subject);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void unbindSubject() {
|
|
||||||
ThreadContext.unbindSubject();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldMapTheUpdateLink() {
|
|
||||||
User user = createDefaultUser();
|
|
||||||
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
|
||||||
|
|
||||||
UserDto userDto = mapper.map(user);
|
|
||||||
assertEquals("expected update link", expectedUserBaseUri.resolve("abc").toString(), userDto.getLinks().getLinkBy("update").get().getHref());
|
|
||||||
|
|
||||||
when(subject.isPermitted("user:modify:abc")).thenReturn(false);
|
|
||||||
userDto = mapper.map(user);
|
|
||||||
assertFalse("expected no update link", userDto.getLinks().getLinkBy("update").isPresent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldMapTheSelfLink() {
|
|
||||||
User user = createDefaultUser();
|
|
||||||
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
|
||||||
|
|
||||||
UserDto userDto = mapper.map(user);
|
|
||||||
assertEquals("expected self link", expectedBaseUri.toString(), userDto.getLinks().getLinkBy("self").get().getHref());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldMapTheDeleteLink() {
|
|
||||||
User user = createDefaultUser();
|
|
||||||
when(subject.isPermitted("user:delete:abc")).thenReturn(true);
|
|
||||||
|
|
||||||
UserDto userDto = mapper.map(user);
|
|
||||||
assertEquals("expected update link", expectedUserBaseUri.resolve("abc").toString(), userDto.getLinks().getLinkBy("delete").get().getHref());
|
|
||||||
|
|
||||||
when(subject.isPermitted("user:delete:abc")).thenReturn(false);
|
|
||||||
userDto = mapper.map(user);
|
|
||||||
assertFalse("expected no delete link", userDto.getLinks().getLinkBy("delete").isPresent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldGetPasswordLinkOnlyForDefaultUserType() {
|
|
||||||
User user = createDefaultUser();
|
|
||||||
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
|
||||||
when(userManager.isTypeDefault(eq(user))).thenReturn(true);
|
|
||||||
|
|
||||||
UserDto userDto = mapper.map(user);
|
|
||||||
|
|
||||||
assertEquals("expected password link with modify permission", expectedBaseUri.resolve("password").toString(), userDto.getLinks().getLinkBy("password").get().getHref());
|
|
||||||
|
|
||||||
when(subject.isPermitted("user:modify:abc")).thenReturn(false);
|
|
||||||
userDto = mapper.map(user);
|
|
||||||
assertEquals("expected password link on mission modify permission", expectedBaseUri.resolve("password").toString(), userDto.getLinks().getLinkBy("password").get().getHref());
|
|
||||||
|
|
||||||
when(userManager.isTypeDefault(eq(user))).thenReturn(false);
|
|
||||||
|
|
||||||
userDto = mapper.map(user);
|
|
||||||
|
|
||||||
assertFalse("expected no password link", userDto.getLinks().getLinkBy("password").isPresent());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldGetEmptyPasswordProperty() {
|
|
||||||
User user = createDefaultUser();
|
|
||||||
user.setPassword("myHighSecurePassword");
|
|
||||||
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
|
||||||
|
|
||||||
UserDto userDto = mapper.map(user);
|
|
||||||
|
|
||||||
assertThat(userDto.getPassword()).as("hide password for the me resource").isBlank();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldAppendLinks() {
|
|
||||||
LinkEnricherRegistry registry = new LinkEnricherRegistry();
|
|
||||||
registry.register(Me.class, (ctx, appender) -> {
|
|
||||||
User user = ctx.oneRequireByType(User.class);
|
|
||||||
appender.appendOne("profile", "http://hitchhiker.com/users/" + user.getName());
|
|
||||||
});
|
|
||||||
mapper.setRegistry(registry);
|
|
||||||
|
|
||||||
User trillian = UserTestData.createTrillian();
|
|
||||||
UserDto dto = mapper.map(trillian);
|
|
||||||
|
|
||||||
assertEquals("http://hitchhiker.com/users/trillian", dto.getLinks().getLinkBy("profile").get().getHref());
|
|
||||||
}
|
|
||||||
|
|
||||||
private User createDefaultUser() {
|
|
||||||
User user = new User();
|
|
||||||
user.setName("abc");
|
|
||||||
user.setCreationDate(1L);
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -29,7 +29,7 @@ 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.Permission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.PermissionType;
|
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;
|
||||||
@@ -58,7 +58,7 @@ import static org.mockito.Mockito.mock;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.mockito.MockitoAnnotations.initMocks;
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
||||||
import static sonia.scm.api.v2.resources.PermissionDto.GROUP_PREFIX;
|
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@SubjectAware(
|
@SubjectAware(
|
||||||
@@ -77,14 +77,14 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
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\", \"type\" : \"READ\" }";
|
||||||
private static final ArrayList<Permission> TEST_PERMISSIONS = Lists
|
private static final ArrayList<RepositoryPermission> TEST_PERMISSIONS = Lists
|
||||||
.newArrayList(
|
.newArrayList(
|
||||||
new Permission("user_write", PermissionType.WRITE, false),
|
new RepositoryPermission("user_write", PermissionType.WRITE, false),
|
||||||
new Permission("user_read", PermissionType.READ, false),
|
new RepositoryPermission("user_read", PermissionType.READ, false),
|
||||||
new Permission("user_owner", PermissionType.OWNER, false),
|
new RepositoryPermission("user_owner", PermissionType.OWNER, false),
|
||||||
new Permission("group_read", PermissionType.READ, true),
|
new RepositoryPermission("group_read", PermissionType.READ, true),
|
||||||
new Permission("group_write", PermissionType.WRITE, true),
|
new RepositoryPermission("group_write", PermissionType.WRITE, true),
|
||||||
new Permission("group_owner", PermissionType.OWNER, true)
|
new RepositoryPermission("group_owner", PermissionType.OWNER, true)
|
||||||
);
|
);
|
||||||
private final ExpectedRequest requestGETAllPermissions = new ExpectedRequest()
|
private final ExpectedRequest requestGETAllPermissions = new ExpectedRequest()
|
||||||
.description("GET all permissions")
|
.description("GET all permissions")
|
||||||
@@ -121,12 +121,12 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private PermissionToPermissionDtoMapperImpl permissionToPermissionDtoMapper;
|
private RepositoryPermissionToRepositoryPermissionDtoMapperImpl permissionToPermissionDtoMapper;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private PermissionDtoToPermissionMapperImpl permissionDtoToPermissionMapper;
|
private PermissionDtoToPermissionMapperImpl permissionDtoToPermissionMapper;
|
||||||
|
|
||||||
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
private RepositoryPermissionCollectionToDtoMapper repositoryPermissionCollectionToDtoMapper;
|
||||||
|
|
||||||
private PermissionRootResource permissionRootResource;
|
private PermissionRootResource permissionRootResource;
|
||||||
|
|
||||||
@@ -137,8 +137,8 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() {
|
public void prepareEnvironment() {
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
permissionCollectionToDtoMapper = new PermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks);
|
repositoryPermissionCollectionToDtoMapper = new RepositoryPermissionCollectionToDtoMapper(permissionToPermissionDtoMapper, resourceLinks);
|
||||||
permissionRootResource = new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, permissionCollectionToDtoMapper, resourceLinks, repositoryManager);
|
permissionRootResource = new PermissionRootResource(permissionDtoToPermissionMapper, permissionToPermissionDtoMapper, repositoryPermissionCollectionToDtoMapper, resourceLinks, repositoryManager);
|
||||||
super.permissionRootResource = Providers.of(permissionRootResource);
|
super.permissionRootResource = Providers.of(permissionRootResource);
|
||||||
dispatcher = createDispatcher(getRepositoryRootResource());
|
dispatcher = createDispatcher(getRepositoryRootResource());
|
||||||
subjectThreadState.bind();
|
subjectThreadState.bind();
|
||||||
@@ -207,7 +207,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldGetPermissionByName() throws URISyntaxException {
|
public void shouldGetPermissionByName() throws URISyntaxException {
|
||||||
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_READ);
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_READ);
|
||||||
Permission expectedPermission = TEST_PERMISSIONS.get(0);
|
RepositoryPermission expectedPermission = TEST_PERMISSIONS.get(0);
|
||||||
assertExpectedRequest(requestGETPermission
|
assertExpectedRequest(requestGETPermission
|
||||||
.expectedResponseStatus(200)
|
.expectedResponseStatus(200)
|
||||||
.path(PATH_OF_ALL_PERMISSIONS + expectedPermission.getName())
|
.path(PATH_OF_ALL_PERMISSIONS + expectedPermission.getName())
|
||||||
@@ -215,8 +215,8 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
String body = response.getContentAsString();
|
String body = response.getContentAsString();
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
try {
|
try {
|
||||||
PermissionDto actualPermissionDto = mapper.readValue(body, PermissionDto.class);
|
RepositoryPermissionDto actualRepositoryPermissionDto = mapper.readValue(body, RepositoryPermissionDto.class);
|
||||||
assertThat(actualPermissionDto)
|
assertThat(actualRepositoryPermissionDto)
|
||||||
.as("response payload match permission object model")
|
.as("response payload match permission object model")
|
||||||
.isEqualToComparingFieldByFieldRecursively(getExpectedPermissionDto(expectedPermission, PERMISSION_READ))
|
.isEqualToComparingFieldByFieldRecursively(getExpectedPermissionDto(expectedPermission, PERMISSION_READ))
|
||||||
;
|
;
|
||||||
@@ -259,10 +259,10 @@ 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);
|
||||||
Permission newPermission = new Permission("new_group_perm", PermissionType.WRITE, true);
|
RepositoryPermission newPermission = new RepositoryPermission("new_group_perm", PermissionType.WRITE, true);
|
||||||
ArrayList<Permission> permissions = Lists.newArrayList(TEST_PERMISSIONS);
|
ArrayList<RepositoryPermission> permissions = Lists.newArrayList(TEST_PERMISSIONS);
|
||||||
permissions.add(newPermission);
|
permissions.add(newPermission);
|
||||||
ImmutableList<Permission> 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() + "\" , \"type\" : \"WRITE\" , \"groupPermission\" : true}")
|
||||||
.expectedResponseStatus(201)
|
.expectedResponseStatus(201)
|
||||||
@@ -276,7 +276,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldNotAddExistingPermission() throws URISyntaxException {
|
public void shouldNotAddExistingPermission() throws URISyntaxException {
|
||||||
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
||||||
Permission 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() + "\" , \"type\" : \"WRITE\" , \"groupPermission\" : false}")
|
||||||
.expectedResponseStatus(409)
|
.expectedResponseStatus(409)
|
||||||
@@ -286,10 +286,10 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldGetUpdatedPermissions() throws URISyntaxException {
|
public void shouldGetUpdatedPermissions() throws URISyntaxException {
|
||||||
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
||||||
Permission 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.setType(PermissionType.OWNER);
|
||||||
ImmutableList<Permission> 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() + "\" , \"type\" : \"OWNER\" , \"groupPermission\" : false}")
|
||||||
.path(PATH_OF_ALL_PERMISSIONS + modifiedPermission.getName())
|
.path(PATH_OF_ALL_PERMISSIONS + modifiedPermission.getName())
|
||||||
@@ -305,8 +305,8 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldDeletePermissions() throws URISyntaxException {
|
public void shouldDeletePermissions() throws URISyntaxException {
|
||||||
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
|
||||||
Permission deletedPermission = TEST_PERMISSIONS.get(0);
|
RepositoryPermission deletedPermission = TEST_PERMISSIONS.get(0);
|
||||||
ImmutableList<Permission> expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS.subList(1, TEST_PERMISSIONS.size()));
|
ImmutableList<RepositoryPermission> expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS.subList(1, TEST_PERMISSIONS.size()));
|
||||||
assertExpectedRequest(requestDELETEPermission
|
assertExpectedRequest(requestDELETEPermission
|
||||||
.path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName())
|
.path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName())
|
||||||
.expectedResponseStatus(204)
|
.expectedResponseStatus(204)
|
||||||
@@ -320,8 +320,8 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void deletingNotExistingPermissionShouldProcess() throws URISyntaxException {
|
public void deletingNotExistingPermissionShouldProcess() throws URISyntaxException {
|
||||||
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
|
||||||
Permission deletedPermission = TEST_PERMISSIONS.get(0);
|
RepositoryPermission deletedPermission = TEST_PERMISSIONS.get(0);
|
||||||
ImmutableList<Permission> expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS.subList(1, TEST_PERMISSIONS.size()));
|
ImmutableList<RepositoryPermission> expectedPermissions = ImmutableList.copyOf(TEST_PERMISSIONS.subList(1, TEST_PERMISSIONS.size()));
|
||||||
assertExpectedRequest(requestDELETEPermission
|
assertExpectedRequest(requestDELETEPermission
|
||||||
.path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName())
|
.path(PATH_OF_ALL_PERMISSIONS + deletedPermission.getName())
|
||||||
.expectedResponseStatus(204)
|
.expectedResponseStatus(204)
|
||||||
@@ -340,7 +340,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
assertGettingExpectedPermissions(expectedPermissions, PERMISSION_READ);
|
assertGettingExpectedPermissions(expectedPermissions, PERMISSION_READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertGettingExpectedPermissions(ImmutableList<Permission> expectedPermissions, String userPermission) throws URISyntaxException {
|
private void assertGettingExpectedPermissions(ImmutableList<RepositoryPermission> expectedPermissions, String userPermission) throws URISyntaxException {
|
||||||
assertExpectedRequest(requestGETAllPermissions
|
assertExpectedRequest(requestGETAllPermissions
|
||||||
.expectedResponseStatus(200)
|
.expectedResponseStatus(200)
|
||||||
.responseValidator((response) -> {
|
.responseValidator((response) -> {
|
||||||
@@ -349,16 +349,16 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
try {
|
try {
|
||||||
HalRepresentation halRepresentation = mapper.readValue(body, HalRepresentation.class);
|
HalRepresentation halRepresentation = mapper.readValue(body, HalRepresentation.class);
|
||||||
List<HalRepresentation> actualPermissionDtos = halRepresentation.getEmbedded().getItemsBy("permissions", HalRepresentation.class);
|
List<HalRepresentation> actualPermissionDtos = halRepresentation.getEmbedded().getItemsBy("permissions", HalRepresentation.class);
|
||||||
List<PermissionDto> permissionDtoStream = actualPermissionDtos.stream()
|
List<RepositoryPermissionDto> repositoryPermissionDtoStream = actualPermissionDtos.stream()
|
||||||
.map(hal -> {
|
.map(hal -> {
|
||||||
PermissionDto result = new PermissionDto();
|
RepositoryPermissionDto result = new RepositoryPermissionDto();
|
||||||
result.setName(hal.getAttribute("name").asText());
|
result.setName(hal.getAttribute("name").asText());
|
||||||
result.setType(hal.getAttribute("type").asText());
|
result.setType(hal.getAttribute("type").asText());
|
||||||
result.setGroupPermission(hal.getAttribute("groupPermission").asBoolean());
|
result.setGroupPermission(hal.getAttribute("groupPermission").asBoolean());
|
||||||
result.add(hal.getLinks());
|
result.add(hal.getLinks());
|
||||||
return result;
|
return result;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
assertThat(permissionDtoStream)
|
assertThat(repositoryPermissionDtoStream)
|
||||||
.as("response payload match permission object models")
|
.as("response payload match permission object models")
|
||||||
.hasSize(expectedPermissions.size())
|
.hasSize(expectedPermissions.size())
|
||||||
.usingRecursiveFieldByFieldElementComparator()
|
.usingRecursiveFieldByFieldElementComparator()
|
||||||
@@ -371,15 +371,15 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PermissionDto[] getExpectedPermissionDtos(ArrayList<Permission> permissions, String userPermission) {
|
private RepositoryPermissionDto[] getExpectedPermissionDtos(ArrayList<RepositoryPermission> permissions, String userPermission) {
|
||||||
return permissions
|
return permissions
|
||||||
.stream()
|
.stream()
|
||||||
.map(p -> getExpectedPermissionDto(p, userPermission))
|
.map(p -> getExpectedPermissionDto(p, userPermission))
|
||||||
.toArray(PermissionDto[]::new);
|
.toArray(RepositoryPermissionDto[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PermissionDto getExpectedPermissionDto(Permission permission, String userPermission) {
|
private RepositoryPermissionDto getExpectedPermissionDto(RepositoryPermission permission, String userPermission) {
|
||||||
PermissionDto result = new PermissionDto();
|
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.setType(permission.getType().name());
|
||||||
@@ -411,7 +411,7 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
return mockRepository;
|
return mockRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createUserWithRepositoryAndPermissions(ArrayList<Permission> permissions, String userPermission) {
|
private void createUserWithRepositoryAndPermissions(ArrayList<RepositoryPermission> permissions, String userPermission) {
|
||||||
createUserWithRepository(userPermission).setPermissions(permissions);
|
createUserWithRepository(userPermission).setPermissions(permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
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.Permission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.PermissionType;
|
import sonia.scm.repository.PermissionType;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
@SubjectAware(
|
@SubjectAware(
|
||||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||||
)
|
)
|
||||||
public class PermissionToPermissionDtoMapperTest {
|
public class RepositoryPermissionToRepositoryPermissionDtoMapperTest {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ShiroRule shiro = new ShiroRule();
|
public ShiroRule shiro = new ShiroRule();
|
||||||
@@ -30,31 +30,31 @@ public class PermissionToPermissionDtoMapperTest {
|
|||||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
PermissionToPermissionDtoMapperImpl mapper;
|
RepositoryPermissionToRepositoryPermissionDtoMapperImpl mapper;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
@SubjectAware(username = "trillian", password = "secret")
|
||||||
public void shouldMapGroupPermissionCorrectly() {
|
public void shouldMapGroupPermissionCorrectly() {
|
||||||
Repository repository = getDummyRepository();
|
Repository repository = getDummyRepository();
|
||||||
Permission permission = new Permission("42", PermissionType.OWNER, true);
|
RepositoryPermission permission = new RepositoryPermission("42", PermissionType.OWNER, true);
|
||||||
|
|
||||||
PermissionDto permissionDto = mapper.map(permission, repository);
|
RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository);
|
||||||
|
|
||||||
assertThat(permissionDto.getLinks().getLinkBy("self").isPresent()).isTrue();
|
assertThat(repositoryPermissionDto.getLinks().getLinkBy("self").isPresent()).isTrue();
|
||||||
assertThat(permissionDto.getLinks().getLinkBy("self").get().getHref()).contains("@42");
|
assertThat(repositoryPermissionDto.getLinks().getLinkBy("self").get().getHref()).contains("@42");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SubjectAware(username = "trillian", password = "secret")
|
@SubjectAware(username = "trillian", password = "secret")
|
||||||
public void shouldMapNonGroupPermissionCorrectly() {
|
public void shouldMapNonGroupPermissionCorrectly() {
|
||||||
Repository repository = getDummyRepository();
|
Repository repository = getDummyRepository();
|
||||||
Permission permission = new Permission("42", PermissionType.OWNER, false);
|
RepositoryPermission permission = new RepositoryPermission("42", PermissionType.OWNER, false);
|
||||||
|
|
||||||
PermissionDto permissionDto = mapper.map(permission, repository);
|
RepositoryPermissionDto repositoryPermissionDto = mapper.map(permission, repository);
|
||||||
|
|
||||||
assertThat(permissionDto.getLinks().getLinkBy("self").isPresent()).isTrue();
|
assertThat(repositoryPermissionDto.getLinks().getLinkBy("self").isPresent()).isTrue();
|
||||||
assertThat(permissionDto.getLinks().getLinkBy("self").get().getHref()).contains("42");
|
assertThat(repositoryPermissionDto.getLinks().getLinkBy("self").get().getHref()).contains("42");
|
||||||
assertThat(permissionDto.getLinks().getLinkBy("self").get().getHref()).doesNotContain("@");
|
assertThat(repositoryPermissionDto.getLinks().getLinkBy("self").get().getHref()).doesNotContain("@");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Repository getDummyRepository() {
|
private Repository getDummyRepository() {
|
||||||
@@ -18,7 +18,7 @@ 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.Permission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.PermissionType;
|
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;
|
||||||
@@ -302,7 +302,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldNotOverwriteExistingPermissionsOnUpdate() throws Exception {
|
public void shouldNotOverwriteExistingPermissionsOnUpdate() throws Exception {
|
||||||
Repository existingRepository = mockRepository("space", "repo");
|
Repository existingRepository = mockRepository("space", "repo");
|
||||||
existingRepository.setPermissions(singletonList(new Permission("user", PermissionType.READ)));
|
existingRepository.setPermissions(singletonList(new RepositoryPermission("user", PermissionType.READ)));
|
||||||
|
|
||||||
URL url = Resources.getResource("sonia/scm/api/v2/repository-test-update.json");
|
URL url = Resources.getResource("sonia/scm/api/v2/repository-test-update.json");
|
||||||
byte[] repository = Resources.toByteArray(url);
|
byte[] repository = Resources.toByteArray(url);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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.Permission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.PermissionType;
|
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;
|
||||||
@@ -238,7 +238,7 @@ 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 Permission("permission", PermissionType.READ)));
|
repository.setPermissions(singletonList(new RepositoryPermission("permission", PermissionType.READ)));
|
||||||
|
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ public class ResourceLinksMock {
|
|||||||
when(resourceLinks.user()).thenReturn(userLinks);
|
when(resourceLinks.user()).thenReturn(userLinks);
|
||||||
when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(uriInfo,userLinks));
|
when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(uriInfo,userLinks));
|
||||||
when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(uriInfo));
|
when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(uriInfo));
|
||||||
|
when(resourceLinks.userPermissions()).thenReturn(new ResourceLinks.UserPermissionLinks(uriInfo));
|
||||||
when(resourceLinks.autoComplete()).thenReturn(new ResourceLinks.AutoCompleteLinks(uriInfo));
|
when(resourceLinks.autoComplete()).thenReturn(new ResourceLinks.AutoCompleteLinks(uriInfo));
|
||||||
when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(uriInfo));
|
when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(uriInfo));
|
||||||
when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo));
|
when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo));
|
||||||
|
when(resourceLinks.groupPermissions()).thenReturn(new ResourceLinks.GroupPermissionLinks(uriInfo));
|
||||||
when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(uriInfo));
|
when(resourceLinks.repository()).thenReturn(new ResourceLinks.RepositoryLinks(uriInfo));
|
||||||
when(resourceLinks.incoming()).thenReturn(new ResourceLinks.IncomingLinks(uriInfo));
|
when(resourceLinks.incoming()).thenReturn(new ResourceLinks.IncomingLinks(uriInfo));
|
||||||
when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(uriInfo));
|
when(resourceLinks.repositoryCollection()).thenReturn(new ResourceLinks.RepositoryCollectionLinks(uriInfo));
|
||||||
@@ -27,7 +29,7 @@ public class ResourceLinksMock {
|
|||||||
when(resourceLinks.changeset()).thenReturn(new ResourceLinks.ChangesetLinks(uriInfo));
|
when(resourceLinks.changeset()).thenReturn(new ResourceLinks.ChangesetLinks(uriInfo));
|
||||||
when(resourceLinks.fileHistory()).thenReturn(new ResourceLinks.FileHistoryLinks(uriInfo));
|
when(resourceLinks.fileHistory()).thenReturn(new ResourceLinks.FileHistoryLinks(uriInfo));
|
||||||
when(resourceLinks.source()).thenReturn(new ResourceLinks.SourceLinks(uriInfo));
|
when(resourceLinks.source()).thenReturn(new ResourceLinks.SourceLinks(uriInfo));
|
||||||
when(resourceLinks.permission()).thenReturn(new ResourceLinks.PermissionLinks(uriInfo));
|
when(resourceLinks.repositoryPermission()).thenReturn(new ResourceLinks.RepositoryPermissionLinks(uriInfo));
|
||||||
when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo));
|
when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo));
|
||||||
when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(uriInfo));
|
when(resourceLinks.branch()).thenReturn(new ResourceLinks.BranchLinks(uriInfo));
|
||||||
when(resourceLinks.diff()).thenReturn(new ResourceLinks.DiffLinks(uriInfo));
|
when(resourceLinks.diff()).thenReturn(new ResourceLinks.DiffLinks(uriInfo));
|
||||||
@@ -39,6 +41,7 @@ public class ResourceLinksMock {
|
|||||||
when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo));
|
when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo));
|
||||||
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));
|
||||||
|
|
||||||
return resourceLinks;
|
return resourceLinks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ import org.junit.Test;
|
|||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Spy;
|
||||||
import sonia.scm.ContextEntry;
|
import sonia.scm.ContextEntry;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
|
import sonia.scm.security.PermissionAssigner;
|
||||||
|
import sonia.scm.security.PermissionDescriptor;
|
||||||
import sonia.scm.user.ChangePasswordNotAllowedException;
|
import sonia.scm.user.ChangePasswordNotAllowedException;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
@@ -26,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -59,10 +63,14 @@ public class UserRootResourceTest {
|
|||||||
private PasswordService passwordService;
|
private PasswordService passwordService;
|
||||||
@Mock
|
@Mock
|
||||||
private UserManager userManager;
|
private UserManager userManager;
|
||||||
|
@Mock
|
||||||
|
private PermissionAssigner permissionAssigner;
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private UserDtoToUserMapperImpl dtoToUserMapper;
|
private UserDtoToUserMapperImpl dtoToUserMapper;
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private UserToUserDtoMapperImpl userToDtoMapper;
|
private UserToUserDtoMapperImpl userToDtoMapper;
|
||||||
|
@InjectMocks
|
||||||
|
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
||||||
|
|
||||||
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||||
private User originalUser;
|
private User originalUser;
|
||||||
@@ -80,7 +88,8 @@ public class UserRootResourceTest {
|
|||||||
UserCollectionToDtoMapper userCollectionToDtoMapper = new UserCollectionToDtoMapper(userToDtoMapper, resourceLinks);
|
UserCollectionToDtoMapper userCollectionToDtoMapper = new UserCollectionToDtoMapper(userToDtoMapper, resourceLinks);
|
||||||
UserCollectionResource userCollectionResource = new UserCollectionResource(userManager, dtoToUserMapper,
|
UserCollectionResource userCollectionResource = new UserCollectionResource(userManager, dtoToUserMapper,
|
||||||
userCollectionToDtoMapper, resourceLinks, passwordService);
|
userCollectionToDtoMapper, resourceLinks, passwordService);
|
||||||
UserResource userResource = new UserResource(dtoToUserMapper, userToDtoMapper, userManager, passwordService);
|
UserPermissionResource userPermissionResource = new UserPermissionResource(permissionAssigner, permissionCollectionToDtoMapper);
|
||||||
|
UserResource userResource = new UserResource(dtoToUserMapper, userToDtoMapper, userManager, passwordService, userPermissionResource);
|
||||||
UserRootResource userRootResource = new UserRootResource(Providers.of(userCollectionResource),
|
UserRootResource userRootResource = new UserRootResource(Providers.of(userCollectionResource),
|
||||||
Providers.of(userResource));
|
Providers.of(userResource));
|
||||||
|
|
||||||
@@ -330,8 +339,6 @@ public class UserRootResourceTest {
|
|||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
System.out.println(response.getContentAsString());
|
|
||||||
|
|
||||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
||||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/?page=0"));
|
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/?page=0"));
|
||||||
@@ -348,8 +355,6 @@ public class UserRootResourceTest {
|
|||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
System.out.println(response.getContentAsString());
|
|
||||||
|
|
||||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
||||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/?page=1"));
|
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/?page=1"));
|
||||||
@@ -359,6 +364,48 @@ public class UserRootResourceTest {
|
|||||||
assertTrue(response.getContentAsString().contains("\"last\":{\"href\":\"/v2/users/?page=2"));
|
assertTrue(response.getContentAsString().contains("\"last\":{\"href\":\"/v2/users/?page=2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetPermissionLink() throws URISyntaxException {
|
||||||
|
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo");
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
|
|
||||||
|
assertTrue(response.getContentAsString().contains("\"permissions\":{"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetPermissions() throws URISyntaxException {
|
||||||
|
when(permissionAssigner.readPermissionsForUser("Neo")).thenReturn(singletonList(new PermissionDescriptor("something:*")));
|
||||||
|
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo/permissions");
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
|
|
||||||
|
assertTrue(response.getContentAsString().contains("\"permissions\":[\"something:*\"]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSetPermissions() throws URISyntaxException {
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.put("/" + UserRootResource.USERS_PATH_V2 + "Neo/permissions")
|
||||||
|
.contentType(VndMediaType.PERMISSION_COLLECTION)
|
||||||
|
.content("{\"permissions\":[\"other:*\"]}".getBytes());
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
ArgumentCaptor<Collection<PermissionDescriptor>> captor = ArgumentCaptor.forClass(Collection.class);
|
||||||
|
doNothing().when(permissionAssigner).setPermissionsForUser(eq("Neo"), captor.capture());
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||||
|
|
||||||
|
assertEquals("other:*", captor.getValue().iterator().next().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
private PageResult<User> createSingletonPageResult(int overallCount) {
|
private PageResult<User> createSingletonPageResult(int overallCount) {
|
||||||
return new PageResult<>(singletonList(createDummyUser("Neo")), overallCount);
|
return new PageResult<>(singletonList(createDummyUser("Neo")), overallCount);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ 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);
|
Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number);
|
||||||
repository.addPermission(new Permission("trillian", PermissionType.READ));
|
repository.addPermission(new RepositoryPermission("trillian", PermissionType.READ));
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ 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;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserEvent;
|
import sonia.scm.user.UserEvent;
|
||||||
@@ -173,10 +174,10 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
{
|
{
|
||||||
Repository repositoryModified = RepositoryTestData.createHeartOfGold();
|
Repository repositoryModified = RepositoryTestData.createHeartOfGold();
|
||||||
repositoryModified.setName("test123");
|
repositoryModified.setName("test123");
|
||||||
repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
|
repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test")));
|
||||||
|
|
||||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
Repository repository = RepositoryTestData.createHeartOfGold();
|
||||||
repository.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
|
repository.setPermissions(Lists.newArrayList(new RepositoryPermission("test")));
|
||||||
|
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.BEFORE_CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.BEFORE_CREATE, repositoryModified, repository));
|
||||||
assertEventIsNotFired();
|
assertEventIsNotFired();
|
||||||
@@ -184,18 +185,18 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
||||||
assertEventIsNotFired();
|
assertEventIsNotFired();
|
||||||
|
|
||||||
repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
|
repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test")));
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
||||||
assertEventIsNotFired();
|
assertEventIsNotFired();
|
||||||
|
|
||||||
repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test123")));
|
repositoryModified.setPermissions(Lists.newArrayList(new RepositoryPermission("test123")));
|
||||||
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 sonia.scm.repository.Permission("test", PermissionType.READ, true))
|
Lists.newArrayList(new RepositoryPermission("test", PermissionType.READ, true))
|
||||||
);
|
);
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
||||||
assertGlobalEventIsFired();
|
assertGlobalEventIsFired();
|
||||||
@@ -203,7 +204,7 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
resetStoredEvent();
|
resetStoredEvent();
|
||||||
|
|
||||||
repositoryModified.setPermissions(
|
repositoryModified.setPermissions(
|
||||||
Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.WRITE))
|
Lists.newArrayList(new RepositoryPermission("test", PermissionType.WRITE))
|
||||||
);
|
);
|
||||||
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
producer.onEvent(new RepositoryModificationEvent(HandlerEventType.CREATE, repositoryModified, repository));
|
||||||
assertGlobalEventIsFired();
|
assertGlobalEventIsFired();
|
||||||
@@ -214,7 +215,7 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.security.StoredAssignedPermissionEvent)}.
|
* Tests {@link AuthorizationChangedEventProducer#onEvent(AssignedPermissionEvent)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testOnStoredAssignedPermissionEvent()
|
public void testOnStoredAssignedPermissionEvent()
|
||||||
@@ -222,10 +223,10 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
StoredAssignedPermission groupPermission = new StoredAssignedPermission(
|
StoredAssignedPermission groupPermission = new StoredAssignedPermission(
|
||||||
"123", new AssignedPermission("_authenticated", true, "repository:read:*")
|
"123", new AssignedPermission("_authenticated", true, "repository:read:*")
|
||||||
);
|
);
|
||||||
producer.onEvent(new StoredAssignedPermissionEvent(HandlerEventType.BEFORE_CREATE, groupPermission));
|
producer.onEvent(new AssignedPermissionEvent(HandlerEventType.BEFORE_CREATE, groupPermission));
|
||||||
assertEventIsNotFired();
|
assertEventIsNotFired();
|
||||||
|
|
||||||
producer.onEvent(new StoredAssignedPermissionEvent(HandlerEventType.CREATE, groupPermission));
|
producer.onEvent(new AssignedPermissionEvent(HandlerEventType.CREATE, groupPermission));
|
||||||
assertGlobalEventIsFired();
|
assertGlobalEventIsFired();
|
||||||
|
|
||||||
resetStoredEvent();
|
resetStoredEvent();
|
||||||
@@ -233,12 +234,12 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
StoredAssignedPermission userPermission = new StoredAssignedPermission(
|
StoredAssignedPermission userPermission = new StoredAssignedPermission(
|
||||||
"123", new AssignedPermission("trillian", false, "repository:read:*")
|
"123", new AssignedPermission("trillian", false, "repository:read:*")
|
||||||
);
|
);
|
||||||
producer.onEvent(new StoredAssignedPermissionEvent(HandlerEventType.BEFORE_CREATE, userPermission));
|
producer.onEvent(new AssignedPermissionEvent(HandlerEventType.BEFORE_CREATE, userPermission));
|
||||||
assertEventIsNotFired();
|
assertEventIsNotFired();
|
||||||
|
|
||||||
resetStoredEvent();
|
resetStoredEvent();
|
||||||
|
|
||||||
producer.onEvent(new StoredAssignedPermissionEvent(HandlerEventType.CREATE, userPermission));
|
producer.onEvent(new AssignedPermissionEvent(HandlerEventType.CREATE, userPermission));
|
||||||
assertUserEventIsFired("trillian");
|
assertUserEventIsFired("trillian");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,4 +254,4 @@ public class AuthorizationChangedEventProducerTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,15 @@
|
|||||||
|
|
||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import org.apache.shiro.authc.AuthenticationInfo;
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.SimpleAuthenticationInfo;
|
||||||
import org.apache.shiro.authc.UsernamePasswordToken;
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Answers;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
@@ -43,6 +47,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
@@ -65,6 +70,9 @@ class BearerRealmTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private DAORealmHelper realmHelper;
|
private DAORealmHelper realmHelper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private DAORealmHelper.AuthenticationInfoBuilder builder;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private AccessTokenResolver accessTokenResolver;
|
private AccessTokenResolver accessTokenResolver;
|
||||||
|
|
||||||
@@ -84,15 +92,19 @@ class BearerRealmTest {
|
|||||||
void shouldDoGetAuthentication() {
|
void shouldDoGetAuthentication() {
|
||||||
BearerToken bearerToken = BearerToken.valueOf("__bearer__");
|
BearerToken bearerToken = BearerToken.valueOf("__bearer__");
|
||||||
AccessToken accessToken = mock(AccessToken.class);
|
AccessToken accessToken = mock(AccessToken.class);
|
||||||
when(accessToken.getSubject()).thenReturn("trillian");
|
|
||||||
when(accessToken.getClaims()).thenReturn(new HashMap<>());
|
|
||||||
|
|
||||||
|
Set<String> groups = ImmutableSet.of("HeartOfGold", "Puzzle42");
|
||||||
|
|
||||||
|
when(accessToken.getSubject()).thenReturn("trillian");
|
||||||
|
when(accessToken.getGroups()).thenReturn(groups);
|
||||||
|
when(accessToken.getClaims()).thenReturn(new HashMap<>());
|
||||||
when(accessTokenResolver.resolve(bearerToken)).thenReturn(accessToken);
|
when(accessTokenResolver.resolve(bearerToken)).thenReturn(accessToken);
|
||||||
|
|
||||||
// we have to use answer, because we could not mock the result of Scopes
|
when(realmHelper.authenticationInfoBuilder("trillian")).thenReturn(builder);
|
||||||
when(realmHelper.getAuthenticationInfo(
|
when(builder.withGroups(groups)).thenReturn(builder);
|
||||||
anyString(), anyString(), any(Scope.class)
|
when(builder.withCredentials("__bearer__")).thenReturn(builder);
|
||||||
)).thenAnswer(createAnswer("trillian", "__bearer__", true));
|
when(builder.withScope(any(Scope.class))).thenReturn(builder);
|
||||||
|
when(builder.build()).thenReturn(authenticationInfo);
|
||||||
|
|
||||||
AuthenticationInfo result = realm.doGetAuthenticationInfo(bearerToken);
|
AuthenticationInfo result = realm.doGetAuthenticationInfo(bearerToken);
|
||||||
assertThat(result).isSameAs(authenticationInfo);
|
assertThat(result).isSameAs(authenticationInfo);
|
||||||
@@ -102,25 +114,4 @@ class BearerRealmTest {
|
|||||||
void shouldThrowIllegalArgumentExceptionForWrongTypeOfToken() {
|
void shouldThrowIllegalArgumentExceptionForWrongTypeOfToken() {
|
||||||
assertThrows(IllegalArgumentException.class, () -> realm.doGetAuthenticationInfo(new UsernamePasswordToken()));
|
assertThrows(IllegalArgumentException.class, () -> realm.doGetAuthenticationInfo(new UsernamePasswordToken()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Answer<AuthenticationInfo> createAnswer(String expectedSubject, String expectedCredentials, boolean scopeEmpty) {
|
|
||||||
return (iom) -> {
|
|
||||||
String subject = iom.getArgument(0);
|
|
||||||
assertThat(subject).isEqualTo(expectedSubject);
|
|
||||||
String credentials = iom.getArgument(1);
|
|
||||||
assertThat(credentials).isEqualTo(expectedCredentials);
|
|
||||||
Scope scope = iom.getArgument(2);
|
|
||||||
assertThat(scope.isEmpty()).isEqualTo(scopeEmpty);
|
|
||||||
|
|
||||||
return authenticationInfo;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MyAnswer implements Answer<AuthenticationInfo> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthenticationInfo answer(InvocationOnMock invocationOnMock) throws Throwable {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ package sonia.scm.security;
|
|||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import org.apache.shiro.authz.AuthorizationInfo;
|
import org.apache.shiro.authz.AuthorizationInfo;
|
||||||
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||||
@@ -49,10 +49,12 @@ import org.mockito.Mockito;
|
|||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.cache.Cache;
|
import sonia.scm.cache.Cache;
|
||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.group.GroupNames;
|
import sonia.scm.group.GroupNames;
|
||||||
import sonia.scm.repository.PermissionType;
|
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.RepositoryTestData;
|
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;
|
||||||
@@ -76,6 +78,8 @@ import static org.mockito.Mockito.when;
|
|||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class DefaultAuthorizationCollectorTest {
|
public class DefaultAuthorizationCollectorTest {
|
||||||
|
|
||||||
|
private ScmConfiguration configuration;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Cache cache;
|
private Cache cache;
|
||||||
|
|
||||||
@@ -99,8 +103,38 @@ public class DefaultAuthorizationCollectorTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp(){
|
public void setUp(){
|
||||||
when(cacheManager.getCache(Mockito.any(String.class))).thenReturn(cache);
|
when(cacheManager.getCache(Mockito.any(String.class))).thenReturn(cache);
|
||||||
|
configuration = new ScmConfiguration();
|
||||||
|
collector = new DefaultAuthorizationCollector(configuration, cacheManager, repositoryDAO, securitySystem);
|
||||||
|
}
|
||||||
|
|
||||||
collector = new DefaultAuthorizationCollector(cacheManager, repositoryDAO, securitySystem);
|
@Test
|
||||||
|
@SubjectAware(
|
||||||
|
configuration = "classpath:sonia/scm/shiro-001.ini"
|
||||||
|
)
|
||||||
|
public void shouldGetAdminPrivilegedByConfiguration() {
|
||||||
|
configuration.setAdminUsers(ImmutableSet.of("trillian"));
|
||||||
|
authenticate(UserTestData.createTrillian(), "main");
|
||||||
|
|
||||||
|
AuthorizationInfo authInfo = collector.collect();
|
||||||
|
assertIsAdmin(authInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIsAdmin(AuthorizationInfo authInfo) {
|
||||||
|
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER, Role.ADMIN));
|
||||||
|
assertThat(authInfo.getObjectPermissions(), nullValue());
|
||||||
|
assertThat(authInfo.getStringPermissions(), Matchers.contains("*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SubjectAware(
|
||||||
|
configuration = "classpath:sonia/scm/shiro-001.ini"
|
||||||
|
)
|
||||||
|
public void shouldGetAdminPrivilegedByGroupConfiguration() {
|
||||||
|
configuration.setAdminGroups(ImmutableSet.of("heartOfGold"));
|
||||||
|
authenticate(UserTestData.createTrillian(), "heartOfGold");
|
||||||
|
|
||||||
|
AuthorizationInfo authInfo = collector.collect();
|
||||||
|
assertIsAdmin(authInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -142,7 +176,7 @@ public class DefaultAuthorizationCollectorTest {
|
|||||||
public void testCollectWithCache() {
|
public void testCollectWithCache() {
|
||||||
authenticate(UserTestData.createTrillian(), "main");
|
authenticate(UserTestData.createTrillian(), "main");
|
||||||
|
|
||||||
AuthorizationInfo authInfo = collector.collect();
|
collector.collect();
|
||||||
verify(cache).put(any(), any());
|
verify(cache).put(any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,9 +210,7 @@ public class DefaultAuthorizationCollectorTest {
|
|||||||
authenticate(trillian, "main");
|
authenticate(trillian, "main");
|
||||||
|
|
||||||
AuthorizationInfo authInfo = collector.collect();
|
AuthorizationInfo authInfo = collector.collect();
|
||||||
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER, Role.ADMIN));
|
assertIsAdmin(authInfo);
|
||||||
assertThat(authInfo.getObjectPermissions(), nullValue());
|
|
||||||
assertThat(authInfo.getStringPermissions(), Matchers.contains("*"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,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 sonia.scm.repository.Permission("trillian")));
|
heartOfGold.setPermissions(Lists.newArrayList(new RepositoryPermission("trillian")));
|
||||||
Repository puzzle42 = RepositoryTestData.create42Puzzle();
|
Repository puzzle42 = RepositoryTestData.create42Puzzle();
|
||||||
puzzle42.setId("two");
|
puzzle42.setId("two");
|
||||||
sonia.scm.repository.Permission permission = new sonia.scm.repository.Permission(group, PermissionType.WRITE, true);
|
RepositoryPermission permission = new RepositoryPermission(group, PermissionType.WRITE, true);
|
||||||
puzzle42.setPermissions(Lists.newArrayList(permission));
|
puzzle42.setPermissions(Lists.newArrayList(permission));
|
||||||
when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42));
|
when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42));
|
||||||
|
|
||||||
@@ -219,7 +251,7 @@ public class DefaultAuthorizationCollectorTest {
|
|||||||
|
|
||||||
StoredAssignedPermission p1 = new StoredAssignedPermission("one", new AssignedPermission("one", "one:one"));
|
StoredAssignedPermission p1 = new StoredAssignedPermission("one", new AssignedPermission("one", "one:one"));
|
||||||
StoredAssignedPermission p2 = new StoredAssignedPermission("two", new AssignedPermission("two", "two:two"));
|
StoredAssignedPermission p2 = new StoredAssignedPermission("two", new AssignedPermission("two", "two:two"));
|
||||||
when(securitySystem.getPermissions(Mockito.any(Predicate.class))).thenReturn(Lists.newArrayList(p1, p2));
|
when(securitySystem.getPermissions(any())).thenReturn(Lists.newArrayList(p1, p2));
|
||||||
|
|
||||||
// execute and assert
|
// execute and assert
|
||||||
AuthorizationInfo authInfo = collector.collect();
|
AuthorizationInfo authInfo = collector.collect();
|
||||||
@@ -238,7 +270,7 @@ public class DefaultAuthorizationCollectorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link AuthorizationCollector#invalidateCache(sonia.scm.security.AuthorizationChangedEvent)}.
|
* Tests {@link DefaultAuthorizationCollector#invalidateCache(sonia.scm.security.AuthorizationChangedEvent)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidateCache() {
|
public void testInvalidateCache() {
|
||||||
@@ -246,7 +278,7 @@ public class DefaultAuthorizationCollectorTest {
|
|||||||
verify(cache).clear();
|
verify(cache).clear();
|
||||||
|
|
||||||
collector.invalidateCache(AuthorizationChangedEvent.createForUser("dent"));
|
collector.invalidateCache(AuthorizationChangedEvent.createForUser("dent"));
|
||||||
verify(cache).removeAll(Mockito.any(Predicate.class));
|
verify(cache).removeAll(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user