merged master

This commit is contained in:
Florian Scholdei
2019-01-23 13:46:46 +01:00
103 changed files with 2630 additions and 1786 deletions

5
Jenkinsfile vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -99,6 +99,15 @@ public interface AccessTokenBuilder {
*/ */
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.
* *

View File

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

View File

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

View File

@@ -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
@@ -111,35 +111,35 @@ public final class DAORealmHelper
} }
/** /**
* 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;

View 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 {
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
/** /**
* 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,
@@ -13,7 +13,7 @@
* 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";
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,5 +6,6 @@ export type Me = {
name: string, name: string,
displayName: string, displayName: string,
mail: string, mail: string,
groups: [],
_links: Links _links: Links
}; };

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
{
"setPermissions": {
"button": "Set permissions",
"setPermissionsSuccessful": "Permissions set successfully"
}
}

View File

@@ -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": {

View File

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

View File

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

View File

@@ -1 +1,2 @@
export { default as GeneralGroupNavLink } from "./GeneralGroupNavLink"; export { default as GeneralGroupNavLink } from "./GeneralGroupNavLink";
export { default as SetPermissionsNavLink } from "./SetPermissionsNavLink";

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
Links.Builder linksBuilder = linkingTo().self(links.permissions(id));
if (PermissionPermissions.assign().isPermitted()) {
linksBuilder.single(link("overwrite", links.overwritePermissions(id)));
} }
private Links createLinks(Repository repository) { target.add(linksBuilder.build());
RepositoryPermissions.permissionRead(repository).check();
Links.Builder linksBuilder = linkingTo()
.with(Links.linkingTo().self(resourceLinks.permission().all(repository.getNamespace(), repository.getName())).build());
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) { return target;
return embeddedBuilder()
.with("permissions", permissionDtoList)
.build();
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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())
&& Objects.equal(sap.getPermission(), permission.getPermission()));
if (deleted) {
ScmEventBus.getInstance().post( ScmEventBus.getInstance().post(
new StoredAssignedPermissionEvent(HandlerEventType.CREATE, permission) new AssignedPermissionEvent(HandlerEventType.DELETE, permission)
); );
//J+
}
/**
* 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,42 +189,9 @@ 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;
} }

View File

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

View File

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

View File

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

View File

@@ -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> </permission>
<value>repository:*:OWNER</value> <permission>
<value>repository:create</value>
</permission>
<permission>
<value>user:*</value>
</permission>
<permission>
<value>group:*</value>
</permission> </permission>
</permissions> </permissions>

View 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"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,28 +32,28 @@
package sonia.scm.security; package sonia.scm.security;
//~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm; import org.apache.shiro.realm.SimpleAccountRealm;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import sonia.scm.AbstractTestBase; import sonia.scm.AbstractTestBase;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.store.JAXBConfigurationEntryStoreFactory; import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
import sonia.scm.util.ClassLoaders;
import sonia.scm.util.MockUtil; import sonia.scm.util.MockUtil;
import static org.hamcrest.Matchers.*; import java.util.Collection;
import static org.junit.Assert.*; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
//~--- JDK imports ------------------------------------------------------------ import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import java.util.List; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
/** /**
* *
@@ -62,6 +62,12 @@ import java.util.List;
public class DefaultSecuritySystemTest extends AbstractTestBase public class DefaultSecuritySystemTest extends AbstractTestBase
{ {
private JAXBConfigurationEntryStoreFactory jaxbConfigurationEntryStoreFactory;
private PluginLoader pluginLoader;
@InjectMocks
private DefaultSecuritySystem securitySystem;
/** /**
* Method description * Method description
* *
@@ -69,12 +75,12 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
@Before @Before
public void createSecuritySystem() public void createSecuritySystem()
{ {
JAXBConfigurationEntryStoreFactory factory = jaxbConfigurationEntryStoreFactory =
new JAXBConfigurationEntryStoreFactory(contextProvider , repositoryLocationResolver, new UUIDKeyGenerator() ); spy(new JAXBConfigurationEntryStoreFactory(contextProvider , repositoryLocationResolver, new UUIDKeyGenerator() ) {});
pluginLoader = mock(PluginLoader.class);
when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class));
securitySystem = new DefaultSecuritySystem(factory); MockitoAnnotations.initMocks(this);
// ScmEventBus.getInstance().register(listener);
} }
/** /**
@@ -86,11 +92,10 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
{ {
setAdminSubject(); setAdminSubject();
StoredAssignedPermission sap = createPermission("trillian", false, AssignedPermission sap = createPermission("trillian", false, "repository:*:READ");
"repository:*:READ");
assertEquals("trillian", sap.getName()); assertEquals("trillian", sap.getName());
assertEquals("repository:*:READ", sap.getPermission()); assertEquals("repository:*:READ", sap.getPermission().getValue());
assertEquals(false, sap.isGroupPermission()); assertEquals(false, sap.isGroupPermission());
} }
@@ -103,10 +108,10 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
{ {
setAdminSubject(); setAdminSubject();
List<PermissionDescriptor> list = securitySystem.getAvailablePermissions(); Collection<PermissionDescriptor> list = securitySystem.getAvailablePermissions();
assertNotNull(list); assertNotNull(list);
assertThat(list.size(), greaterThan(0)); assertThat(list).isNotEmpty();
} }
/** /**
@@ -118,12 +123,12 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
{ {
setAdminSubject(); setAdminSubject();
StoredAssignedPermission sap = createPermission("trillian", false, AssignedPermission sap = createPermission("trillian", false,
"repository:*:READ"); "repository:*:READ");
securitySystem.deletePermission(sap); securitySystem.deletePermission(sap);
assertNull(securitySystem.getPermission(sap.getId())); assertThat(securitySystem.getPermissions(p -> p.getName().equals("trillian"))).isEmpty();
} }
/** /**
@@ -135,17 +140,17 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
{ {
setAdminSubject(); setAdminSubject();
StoredAssignedPermission trillian = createPermission("trillian", false, AssignedPermission trillian = createPermission("trillian", false,
"repository:*:READ"); "repository:*:READ");
StoredAssignedPermission dent = createPermission("dent", false, AssignedPermission dent = createPermission("dent", false,
"repository:*:READ"); "repository:*:READ");
StoredAssignedPermission marvin = createPermission("marvin", false, AssignedPermission marvin = createPermission("marvin", false,
"repository:*:READ"); "repository:*:READ");
List<StoredAssignedPermission> all = securitySystem.getAllPermissions(); Collection<AssignedPermission> all = securitySystem.getPermissions(p -> true);
assertEquals(3, all.size()); assertEquals(3, all.size());
assertThat(all, containsInAnyOrder(trillian, dent, marvin)); assertThat(all).contains(trillian, dent, marvin);
} }
/** /**
@@ -157,13 +162,12 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
{ {
setAdminSubject(); setAdminSubject();
StoredAssignedPermission sap = createPermission("trillian", false, AssignedPermission sap = createPermission("trillian", false,
"repository:*:READ"); "repository:*:READ");
StoredAssignedPermission other = securitySystem.getPermission(sap.getId()); Collection<AssignedPermission> other = securitySystem.getPermissions(p -> p.getName().equals("trillian"));
assertEquals(sap.getId(), other.getId()); assertThat(other).containsExactly(sap);
assertEquals(sap, other);
} }
/** /**
@@ -175,49 +179,19 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
{ {
setAdminSubject(); setAdminSubject();
StoredAssignedPermission trillian = createPermission("trillian", false, AssignedPermission trillian = createPermission("trillian", false,
"repository:*:READ"); "repository:*:READ");
StoredAssignedPermission dent = createPermission("dent", false, AssignedPermission dent = createPermission("dent", false,
"repository:*:READ"); "repository:*:READ");
createPermission("hitchhiker", true, "repository:*:READ"); createPermission("hitchhiker", true, "repository:*:READ");
List<StoredAssignedPermission> filtered = Collection<AssignedPermission> filtered =
securitySystem.getPermissions(new Predicate<AssignedPermission>() securitySystem.getPermissions(p -> !p.isGroupPermission());
{
@Override assertThat(filtered)
public boolean apply(AssignedPermission input) .hasSize(2)
{ .contains(trillian, dent);
return !input.isGroupPermission();
}
});
assertEquals(2, filtered.size());
assertThat(filtered, containsInAnyOrder(trillian, dent));
}
/**
* Method description
*
*/
@Test
public void testModifyPermission()
{
setAdminSubject();
StoredAssignedPermission sap = createPermission("trillian", false,
"repository:*:READ");
StoredAssignedPermission modified =
new StoredAssignedPermission(sap.getId(),
new AssignedPermission("trillian", "repository:*:WRITE"));
securitySystem.modifyPermission(modified);
sap = securitySystem.getPermission(modified.getId());
assertEquals(modified.getId(), sap.getId());
assertEquals(modified, sap);
} }
/** /**
@@ -240,46 +214,13 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
{ {
setAdminSubject(); setAdminSubject();
StoredAssignedPermission sap = createPermission("trillian", false, AssignedPermission sap = createPermission("trillian", false,
"repository:*:READ"); "repository:*:READ");
setUserSubject(); setUserSubject();
securitySystem.deletePermission(sap); securitySystem.deletePermission(sap);
} }
/**
* Method description
*
*/
@Test(expected = UnauthorizedException.class)
public void testUnauthorizedGetPermission()
{
setAdminSubject();
StoredAssignedPermission sap = createPermission("trillian", false,
"repository:*:READ");
setUserSubject();
securitySystem.getPermission(sap.getId());
}
/**
* Method description
*
*/
@Test(expected = UnauthorizedException.class)
public void testUnauthorizedModifyPermission()
{
setAdminSubject();
StoredAssignedPermission sap = createPermission("trillian", false,
"repository:*:READ");
setUserSubject();
securitySystem.modifyPermission(sap);
}
/** /**
* Method description * Method description
* *
@@ -290,17 +231,16 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
* *
* @return * @return
*/ */
private StoredAssignedPermission createPermission(String name, private AssignedPermission createPermission(String name,
boolean groupPermission, String value) boolean groupPermission, String value)
{ {
AssignedPermission ap = new AssignedPermission(name, groupPermission, AssignedPermission ap = new AssignedPermission(name, groupPermission,
value); value);
StoredAssignedPermission sap = securitySystem.addPermission(ap); securitySystem.addPermission(ap);
assertNotNull(sap); return securitySystem.getPermissions(permission -> Objects.equal(name, permission.getName())
assertNotNull(sap.getId()); && Objects.equal(groupPermission, permission.isGroupPermission())
&& Objects.equal(value, permission.getPermission().getValue())).stream().findAny().orElseThrow(() -> new AssertionError("created permission not found"));
return sap;
} }
//~--- set methods ---------------------------------------------------------- //~--- set methods ----------------------------------------------------------
@@ -325,9 +265,4 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
setSubject(MockUtil.createUserSubject(sm)); setSubject(MockUtil.createUserSubject(sm));
} }
//~--- fields ---------------------------------------------------------------
/** Field description */
private DefaultSecuritySystem securitySystem;
} }

Some files were not shown because too many files have changed in this diff Show More