mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 08:25:44 +01:00
Add role attribute to repository permission
This commit is contained in:
@@ -50,6 +50,7 @@ import java.util.LinkedHashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.emptySet;
|
||||||
import static java.util.Collections.unmodifiableSet;
|
import static java.util.Collections.unmodifiableSet;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -73,6 +74,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
private String name;
|
private String name;
|
||||||
@XmlElement(name = "verb")
|
@XmlElement(name = "verb")
|
||||||
private Set<String> verbs;
|
private Set<String> verbs;
|
||||||
|
private String role;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This constructor exists for mapstruct and JAXB, only -- <b>do not use this in "normal" code</b>.
|
* This constructor exists for mapstruct and JAXB, only -- <b>do not use this in "normal" code</b>.
|
||||||
@@ -87,6 +89,15 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.verbs = new LinkedHashSet<>(verbs);
|
this.verbs = new LinkedHashSet<>(verbs);
|
||||||
|
this.role = null;
|
||||||
|
this.groupPermission = groupPermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RepositoryPermission(String name, String role, boolean groupPermission)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.verbs = emptySet();
|
||||||
|
this.role = role;
|
||||||
this.groupPermission = groupPermission;
|
this.groupPermission = groupPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +127,9 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
final RepositoryPermission other = (RepositoryPermission) obj;
|
final RepositoryPermission other = (RepositoryPermission) obj;
|
||||||
|
|
||||||
return Objects.equal(name, other.name)
|
return Objects.equal(name, other.name)
|
||||||
&& verbs.containsAll(other.verbs)
|
|
||||||
&& verbs.size() == other.verbs.size()
|
&& verbs.size() == other.verbs.size()
|
||||||
|
&& verbs.containsAll(other.verbs)
|
||||||
|
&& Objects.equal(role, other.role)
|
||||||
&& Objects.equal(groupPermission, other.groupPermission);
|
&& Objects.equal(groupPermission, other.groupPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +144,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
{
|
{
|
||||||
// Normally we do not have a log of repository permissions having the same size of verbs, but different content.
|
// Normally we do not have a log of repository permissions having the same size of verbs, but different content.
|
||||||
// Therefore we do not use the verbs themselves for the hash code but only the number of verbs.
|
// Therefore we do not use the verbs themselves for the hash code but only the number of verbs.
|
||||||
return Objects.hashCode(name, verbs.size(), groupPermission);
|
return Objects.hashCode(name, verbs.size(), role, groupPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -173,6 +185,16 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
return verbs == null ? emptyList() : Collections.unmodifiableSet(verbs);
|
return verbs == null ? emptyList() : Collections.unmodifiableSet(verbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the role of the permission.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return role of the permission
|
||||||
|
*/
|
||||||
|
public String getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the permission is a permission which affects a group.
|
* Returns true if the permission is a permission which affects a group.
|
||||||
*
|
*
|
||||||
@@ -192,7 +214,8 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
* @throws IllegalStateException when modified after the value has been set once.
|
* @throws IllegalStateException when modified after the value has been set once.
|
||||||
*
|
*
|
||||||
* @deprecated Do not use this for "normal" code.
|
* @deprecated Do not use this for "normal" code.
|
||||||
* Use {@link RepositoryPermission#RepositoryPermission(String, Collection, boolean)} instead.
|
* Use {@link RepositoryPermission#RepositoryPermission(String, Collection, boolean)}
|
||||||
|
* or {@link RepositoryPermission#RepositoryPermission(String, String, boolean)} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void setGroupPermission(boolean groupPermission)
|
public void setGroupPermission(boolean groupPermission)
|
||||||
@@ -208,7 +231,8 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
* @throws IllegalStateException when modified after the value has been set once.
|
* @throws IllegalStateException when modified after the value has been set once.
|
||||||
*
|
*
|
||||||
* @deprecated Do not use this for "normal" code.
|
* @deprecated Do not use this for "normal" code.
|
||||||
* Use {@link RepositoryPermission#RepositoryPermission(String, Collection, boolean)} instead.
|
* Use {@link RepositoryPermission#RepositoryPermission(String, Collection, boolean)}
|
||||||
|
* or {@link RepositoryPermission#RepositoryPermission(String, String, boolean)} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void setName(String name)
|
public void setName(String name)
|
||||||
@@ -219,6 +243,22 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this for creation only. This will throw an {@link IllegalStateException} when modified.
|
||||||
|
* @throws IllegalStateException when modified after the value has been set once.
|
||||||
|
*
|
||||||
|
* @deprecated Do not use this for "normal" code.
|
||||||
|
* Use {@link RepositoryPermission#RepositoryPermission(String, String, boolean)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void setRole(String role)
|
||||||
|
{
|
||||||
|
if (this.role != null) {
|
||||||
|
throw new IllegalStateException(REPOSITORY_MODIFIED_EXCEPTION_TEXT);
|
||||||
|
}
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this for creation only. This will throw an {@link IllegalStateException} when modified.
|
* Use this for creation only. This will throw an {@link IllegalStateException} when modified.
|
||||||
* @throws IllegalStateException when modified after the value has been set once.
|
* @throws IllegalStateException when modified after the value has been set once.
|
||||||
@@ -232,6 +272,6 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
|||||||
if (this.verbs != null) {
|
if (this.verbs != null) {
|
||||||
throw new IllegalStateException(REPOSITORY_MODIFIED_EXCEPTION_TEXT);
|
throw new IllegalStateException(REPOSITORY_MODIFIED_EXCEPTION_TEXT);
|
||||||
}
|
}
|
||||||
this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
|
this.verbs = verbs == null? emptySet(): unmodifiableSet(new LinkedHashSet<>(verbs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import javax.validation.Constraint;
|
||||||
|
import javax.validation.Payload;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.TYPE;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Target({TYPE})
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Constraint(validatedBy = EitherRoleOrVerbsValidator.class)
|
||||||
|
@Documented
|
||||||
|
public @interface EitherRoleOrVerbs {
|
||||||
|
|
||||||
|
String message() default "permission must either have a role or a not empty set of verbs";
|
||||||
|
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintValidator;
|
||||||
|
import javax.validation.ConstraintValidatorContext;
|
||||||
|
|
||||||
|
public class EitherRoleOrVerbsValidator implements ConstraintValidator<EitherRoleOrVerbs, RepositoryPermissionDto> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(EitherRoleOrVerbsValidator.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(EitherRoleOrVerbs constraintAnnotation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(RepositoryPermissionDto object, ConstraintValidatorContext constraintContext) {
|
||||||
|
if (Strings.isNullOrEmpty(object.getRole())) {
|
||||||
|
boolean result = object.getVerbs() != null && !object.getVerbs().isEmpty();
|
||||||
|
LOG.trace("Validation result for permission with empty or no role: {}", result);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
boolean result = object.getVerbs() == null || object.getVerbs().isEmpty();
|
||||||
|
LOG.trace("Validation result for permission with non empty role: {}", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import javax.validation.constraints.Pattern;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
@Getter @Setter @ToString @NoArgsConstructor
|
@Getter @Setter @ToString @NoArgsConstructor
|
||||||
|
@EitherRoleOrVerbs
|
||||||
public class RepositoryPermissionDto extends HalRepresentation {
|
public class RepositoryPermissionDto extends HalRepresentation {
|
||||||
|
|
||||||
public static final String GROUP_PREFIX = "@";
|
public static final String GROUP_PREFIX = "@";
|
||||||
@@ -21,9 +22,11 @@ public class RepositoryPermissionDto extends HalRepresentation {
|
|||||||
@Pattern(regexp = ValidationUtil.REGEX_NAME)
|
@Pattern(regexp = ValidationUtil.REGEX_NAME)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@NotEmpty
|
@NoBlankStrings
|
||||||
private Collection<String> verbs;
|
private Collection<String> verbs;
|
||||||
|
|
||||||
|
private String role;
|
||||||
|
|
||||||
private boolean groupPermission = false;
|
private boolean groupPermission = false;
|
||||||
|
|
||||||
public RepositoryPermissionDto(String permissionName, boolean groupPermission) {
|
public RepositoryPermissionDto(String permissionName, boolean groupPermission) {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package sonia.scm.api.v2.resources;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.inject.util.Providers;
|
import com.google.inject.util.Providers;
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
@@ -21,7 +19,6 @@ import org.jboss.resteasy.mock.MockHttpResponse;
|
|||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
@@ -35,6 +32,7 @@ import sonia.scm.repository.RepositoryManager;
|
|||||||
import sonia.scm.repository.RepositoryPermission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.ws.rs.HttpMethod;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -64,11 +62,6 @@ import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
|||||||
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
|
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@SubjectAware(
|
|
||||||
username = "trillian",
|
|
||||||
password = "secret",
|
|
||||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
|
||||||
)
|
|
||||||
public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
|
public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
|
||||||
private static final String REPOSITORY_NAMESPACE = "repo_namespace";
|
private static final String REPOSITORY_NAMESPACE = "repo_namespace";
|
||||||
private static final String REPOSITORY_NAME = "repo";
|
private static final String REPOSITORY_NAME = "repo";
|
||||||
@@ -114,9 +107,6 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
private Dispatcher dispatcher;
|
private Dispatcher dispatcher;
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiro = new ShiroRule();
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private RepositoryManager repositoryManager;
|
private RepositoryManager repositoryManager;
|
||||||
|
|
||||||
@@ -363,6 +353,69 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
|
|||||||
assertGettingExpectedPermissions(expectedPermissions, PERMISSION_READ);
|
assertGettingExpectedPermissions(expectedPermissions, PERMISSION_READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateValidationErrorForMissingRoleAndEmptyVerbs() throws Exception {
|
||||||
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
HttpRequest request = MockHttpRequest
|
||||||
|
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
||||||
|
.content("{ 'name' : 'permission_name', 'verbs' : [] }".replaceAll("'", "\"").getBytes())
|
||||||
|
.contentType(VndMediaType.REPOSITORY_PERMISSION);
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
assertThat(response.getStatus()).isEqualTo(400);
|
||||||
|
assertThat(response.getContentAsString()).contains("permission must either have a role or a not empty set of verbs");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateValidationErrorForEmptyRoleAndEmptyVerbs() throws Exception {
|
||||||
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
HttpRequest request = MockHttpRequest
|
||||||
|
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
||||||
|
.content("{ 'name' : 'permission_name', 'role': '', 'verbs' : [] }".replaceAll("'", "\"").getBytes())
|
||||||
|
.contentType(VndMediaType.REPOSITORY_PERMISSION);
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
assertThat(response.getStatus()).isEqualTo(400);
|
||||||
|
assertThat(response.getContentAsString()).contains("permission must either have a role or a not empty set of verbs");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateValidationErrorForRoleAndVerbs() throws Exception {
|
||||||
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
HttpRequest request = MockHttpRequest
|
||||||
|
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
||||||
|
.content("{ 'name' : 'permission_name', 'role': 'some role', 'verbs' : ['read'] }".replaceAll("'", "\"").getBytes())
|
||||||
|
.contentType(VndMediaType.REPOSITORY_PERMISSION);
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
assertThat(response.getStatus()).isEqualTo(400);
|
||||||
|
assertThat(response.getContentAsString()).contains("permission must either have a role or a not empty set of verbs");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldPassWithoutValidationErrorForRoleAndEmptyVerbs() throws Exception {
|
||||||
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
HttpRequest request = MockHttpRequest
|
||||||
|
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
||||||
|
.content("{ 'name' : 'permission_name', 'role': 'some role', 'verbs': [] }".replaceAll("'", "\"").getBytes())
|
||||||
|
.contentType(VndMediaType.REPOSITORY_PERMISSION);
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
assertThat(response.getStatus()).isEqualTo(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldPassWithoutValidationErrorForRoleAndNoVerbs() throws Exception {
|
||||||
|
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
HttpRequest request = MockHttpRequest
|
||||||
|
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
||||||
|
.content("{ 'name' : 'permission_name', 'role': 'some role' }".replaceAll("'", "\"").getBytes())
|
||||||
|
.contentType(VndMediaType.REPOSITORY_PERMISSION);
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
assertThat(response.getStatus()).isEqualTo(201);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertGettingExpectedPermissions(ImmutableList<RepositoryPermission> expectedPermissions, String userPermission) throws URISyntaxException {
|
private void assertGettingExpectedPermissions(ImmutableList<RepositoryPermission> expectedPermissions, String userPermission) throws URISyntaxException {
|
||||||
assertExpectedRequest(requestGETAllPermissions
|
assertExpectedRequest(requestGETAllPermissions
|
||||||
.expectedResponseStatus(200)
|
.expectedResponseStatus(200)
|
||||||
|
|||||||
Reference in New Issue
Block a user