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 static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
@@ -73,6 +74,7 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
private String name;
|
||||
@XmlElement(name = "verb")
|
||||
private Set<String> verbs;
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
@@ -116,8 +127,9 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
final RepositoryPermission other = (RepositoryPermission) obj;
|
||||
|
||||
return Objects.equal(name, other.name)
|
||||
&& verbs.containsAll(other.verbs)
|
||||
&& verbs.size() == other.verbs.size()
|
||||
&& verbs.containsAll(other.verbs)
|
||||
&& Objects.equal(role, other.role)
|
||||
&& 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.
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -192,7 +214,8 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
* @throws IllegalStateException when modified after the value has been set once.
|
||||
*
|
||||
* @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
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
public void setName(String name)
|
||||
@@ -219,6 +243,22 @@ public class RepositoryPermission implements PermissionObject, Serializable
|
||||
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.
|
||||
* @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) {
|
||||
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;
|
||||
|
||||
@Getter @Setter @ToString @NoArgsConstructor
|
||||
@EitherRoleOrVerbs
|
||||
public class RepositoryPermissionDto extends HalRepresentation {
|
||||
|
||||
public static final String GROUP_PREFIX = "@";
|
||||
@@ -21,9 +22,11 @@ public class RepositoryPermissionDto extends HalRepresentation {
|
||||
@Pattern(regexp = ValidationUtil.REGEX_NAME)
|
||||
private String name;
|
||||
|
||||
@NotEmpty
|
||||
@NoBlankStrings
|
||||
private Collection<String> verbs;
|
||||
|
||||
private String role;
|
||||
|
||||
private boolean groupPermission = false;
|
||||
|
||||
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.ObjectMapper;
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.util.Providers;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
@@ -21,7 +19,6 @@ import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
@@ -35,6 +32,7 @@ import sonia.scm.repository.RepositoryManager;
|
||||
import sonia.scm.repository.RepositoryPermission;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.ws.rs.HttpMethod;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
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;
|
||||
|
||||
@Slf4j
|
||||
@SubjectAware(
|
||||
username = "trillian",
|
||||
password = "secret",
|
||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||
)
|
||||
public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
|
||||
private static final String REPOSITORY_NAMESPACE = "repo_namespace";
|
||||
private static final String REPOSITORY_NAME = "repo";
|
||||
@@ -114,9 +107,6 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
private Dispatcher dispatcher;
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
@Mock
|
||||
private RepositoryManager repositoryManager;
|
||||
|
||||
@@ -363,6 +353,69 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
|
||||
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 {
|
||||
assertExpectedRequest(requestGETAllPermissions
|
||||
.expectedResponseStatus(200)
|
||||
|
||||
Reference in New Issue
Block a user