diff --git a/pom.xml b/pom.xml
index 944ebb6eb6..df49023265 100644
--- a/pom.xml
+++ b/pom.xml
@@ -188,15 +188,14 @@
test
-
- com.github.sdorra.shiro-static-permissions
+ com.github.sdorra
ssp-lib
${ssp.version}
- com.github.sdorra.shiro-static-permissions
+ com.github.sdorra
ssp-processor
${ssp.version}
true
@@ -765,7 +764,7 @@
9.2.10.v20150310
- 967c8fd521
+ 1.1.0
1.4.0
diff --git a/scm-core/pom.xml b/scm-core/pom.xml
index a3fa037c7b..3c90fec779 100644
--- a/scm-core/pom.xml
+++ b/scm-core/pom.xml
@@ -94,6 +94,12 @@
javax.ws.rs-api
+
+ org.jboss.resteasy
+ resteasy-jaxrs
+ test
+
+
@@ -160,14 +166,13 @@
provided
-
- com.github.sdorra.shiro-static-permissions
+ com.github.sdorra
ssp-lib
- com.github.sdorra.shiro-static-permissions
+ com.github.sdorra
ssp-processor
true
diff --git a/scm-core/src/main/java/sonia/scm/config/Configuration.java b/scm-core/src/main/java/sonia/scm/config/Configuration.java
index e9bf3528d5..823c50b155 100644
--- a/scm-core/src/main/java/sonia/scm/config/Configuration.java
+++ b/scm-core/src/main/java/sonia/scm/config/Configuration.java
@@ -22,7 +22,7 @@ import com.github.sdorra.ssp.StaticPermissions;
@StaticPermissions(
value = "configuration",
permissions = {"read", "write"},
- globalPermissions = {}
+ globalPermissions = {"list"}
)
public interface Configuration extends PermissionObject {
}
diff --git a/scm-core/src/main/java/sonia/scm/group/Group.java b/scm-core/src/main/java/sonia/scm/group/Group.java
index 98d9dcc7a3..5e7f596c58 100644
--- a/scm-core/src/main/java/sonia/scm/group/Group.java
+++ b/scm-core/src/main/java/sonia/scm/group/Group.java
@@ -60,7 +60,7 @@ import java.util.List;
*
* @author Sebastian Sdorra
*/
-@StaticPermissions("group")
+@StaticPermissions(value = "group", globalPermissions = {"create", "list"})
@XmlRootElement(name = "groups")
@XmlAccessorType(XmlAccessType.FIELD)
public class Group extends BasicPropertiesAware
diff --git a/scm-core/src/main/java/sonia/scm/user/User.java b/scm-core/src/main/java/sonia/scm/user/User.java
index 97c6bb16c7..0d909bec8d 100644
--- a/scm-core/src/main/java/sonia/scm/user/User.java
+++ b/scm-core/src/main/java/sonia/scm/user/User.java
@@ -55,11 +55,10 @@ import java.security.Principal;
*
* @author Sebastian Sdorra
*/
-@StaticPermissions("user")
+@StaticPermissions(value = "user", globalPermissions = {"create", "list"})
@XmlRootElement(name = "users")
@XmlAccessorType(XmlAccessType.FIELD)
-public class
-User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject
+public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject
{
/** Field description */
diff --git a/scm-core/src/main/java/sonia/scm/web/JsonEnricherBase.java b/scm-core/src/main/java/sonia/scm/web/JsonEnricherBase.java
new file mode 100644
index 0000000000..1baecb62af
--- /dev/null
+++ b/scm-core/src/main/java/sonia/scm/web/JsonEnricherBase.java
@@ -0,0 +1,40 @@
+package sonia.scm.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.util.Map;
+
+public abstract class JsonEnricherBase implements JsonEnricher {
+
+ private final ObjectMapper objectMapper;
+
+ protected JsonEnricherBase(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ protected boolean resultHasMediaType(String mediaType, JsonEnricherContext context) {
+ return mediaType.equals(context.getResponseMediaType().toString());
+ }
+
+ protected JsonNode value(Object object) {
+ return objectMapper.convertValue(object, JsonNode.class);
+ }
+
+ protected ObjectNode createObject() {
+ return objectMapper.createObjectNode();
+ }
+
+ protected ObjectNode createObject(Map values) {
+ ObjectNode object = createObject();
+
+ values.forEach((key, value) -> object.set(key, value(value)));
+
+ return object;
+ }
+
+ protected void addPropertyNode(JsonNode parent, String newKey, JsonNode child) {
+ ((ObjectNode) parent).set(newKey, child);
+ }
+}
diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
index 14350902a2..f0711cd1e4 100644
--- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
+++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java
@@ -15,6 +15,7 @@ public class VndMediaType {
public static final String PLAIN_TEXT_PREFIX = "text/" + SUBTYPE_PREFIX;
public static final String PLAIN_TEXT_SUFFIX = "+plain;v=" + VERSION;
+ public static final String INDEX = PREFIX + "index" + SUFFIX;
public static final String USER = PREFIX + "user" + SUFFIX;
public static final String GROUP = PREFIX + "group" + SUFFIX;
public static final String REPOSITORY = PREFIX + "repository" + SUFFIX;
diff --git a/scm-core/src/test/java/sonia/scm/web/JsonEnricherBaseTest.java b/scm-core/src/test/java/sonia/scm/web/JsonEnricherBaseTest.java
new file mode 100644
index 0000000000..43ed4940fa
--- /dev/null
+++ b/scm-core/src/test/java/sonia/scm/web/JsonEnricherBaseTest.java
@@ -0,0 +1,51 @@
+package sonia.scm.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Test;
+
+import javax.ws.rs.core.MediaType;
+
+import static java.util.Collections.singletonMap;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class JsonEnricherBaseTest {
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+ private TestJsonEnricher enricher = new TestJsonEnricher(objectMapper);
+
+ @Test
+ public void testResultHasMediaType() {
+ JsonEnricherContext context = new JsonEnricherContext(null, MediaType.APPLICATION_JSON_TYPE, null);
+
+ assertThat(enricher.resultHasMediaType(MediaType.APPLICATION_JSON, context)).isTrue();
+ assertThat(enricher.resultHasMediaType(MediaType.APPLICATION_XML, context)).isFalse();
+ }
+
+ @Test
+ public void testAppendLink() {
+ ObjectNode root = objectMapper.createObjectNode();
+ ObjectNode links = objectMapper.createObjectNode();
+ root.set("_links", links);
+ JsonEnricherContext context = new JsonEnricherContext(null, MediaType.APPLICATION_JSON_TYPE, root);
+ enricher.enrich(context);
+
+ assertThat(links.get("awesome").get("href").asText()).isEqualTo("/my/awesome/link");
+ }
+
+ private static class TestJsonEnricher extends JsonEnricherBase {
+
+ public TestJsonEnricher(ObjectMapper objectMapper) {
+ super(objectMapper);
+ }
+
+ @Override
+ public void enrich(JsonEnricherContext context) {
+ JsonNode gitConfigRefNode = createObject(singletonMap("href", value("/my/awesome/link")));
+
+ addPropertyNode(context.getResponseEntity().get("_links"), "awesome", gitConfigRefNode);
+ }
+ }
+
+}
diff --git a/scm-it/src/test/java/sonia/scm/it/IndexITCase.java b/scm-it/src/test/java/sonia/scm/it/IndexITCase.java
new file mode 100644
index 0000000000..4a621d962f
--- /dev/null
+++ b/scm-it/src/test/java/sonia/scm/it/IndexITCase.java
@@ -0,0 +1,48 @@
+package sonia.scm.it;
+
+import io.restassured.RestAssured;
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+import sonia.scm.it.utils.RestUtil;
+import sonia.scm.web.VndMediaType;
+
+import static sonia.scm.it.utils.RegExMatcher.matchesPattern;
+import static sonia.scm.it.utils.RestUtil.given;
+
+public class IndexITCase {
+
+ @Test
+ public void shouldLinkEverythingForAdmin() {
+ given(VndMediaType.INDEX)
+
+ .when()
+ .get(RestUtil.createResourceUrl(""))
+
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .body(
+ "_links.repositories.href", matchesPattern(".+/repositories/"),
+ "_links.users.href", matchesPattern(".+/users/"),
+ "_links.groups.href", matchesPattern(".+/groups/"),
+ "_links.config.href", matchesPattern(".+/config"),
+ "_links.gitConfig.href", matchesPattern(".+/config/git"),
+ "_links.hgConfig.href", matchesPattern(".+/config/hg"),
+ "_links.svnConfig.href", matchesPattern(".+/config/svn")
+ );
+ }
+
+ @Test
+ public void shouldCreateLoginLinksForAnonymousAccess() {
+ RestAssured.given() // do not specify user credentials
+
+ .when()
+ .get(RestUtil.createResourceUrl(""))
+
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .body(
+ "_links.login.href", matchesPattern(".+/auth/.+")
+ );
+ }
+
+}
diff --git a/scm-it/src/test/java/sonia/scm/it/MeITCase.java b/scm-it/src/test/java/sonia/scm/it/MeITCase.java
index 1c7b5c9e03..64f06765ea 100644
--- a/scm-it/src/test/java/sonia/scm/it/MeITCase.java
+++ b/scm-it/src/test/java/sonia/scm/it/MeITCase.java
@@ -62,18 +62,4 @@ public class MeITCase {
.assertType(s -> assertThat(s).isEqualTo(type))
.assertPasswordLinkDoesNotExists();
}
-
- @Test
- public void shouldGet403IfUserIsNotAdmin() {
- String newUser = "user";
- String password = "pass";
- String type = "xml";
- TestData.createUser(newUser, password, false, type);
- ScmRequests.start()
- .given()
- .url(TestData.getMeUrl())
- .usernameAndPassword(newUser, password)
- .getMeResource()
- .assertStatusCode(403);
- }
}
diff --git a/scm-it/src/test/java/sonia/scm/it/UserITCase.java b/scm-it/src/test/java/sonia/scm/it/UserITCase.java
index 67fe23dcbc..33fbe0cc5d 100644
--- a/scm-it/src/test/java/sonia/scm/it/UserITCase.java
+++ b/scm-it/src/test/java/sonia/scm/it/UserITCase.java
@@ -93,21 +93,4 @@ public class UserITCase {
.assertType(s -> assertThat(s).isEqualTo(type))
.assertPasswordLinkDoesNotExists();
}
-
- @Test
- public void shouldGet403IfUserIsNotAdmin() {
- String newUser = "user";
- String password = "pass";
- String type = "xml";
- TestData.createUser(newUser, password, false, type);
- ScmRequests.start()
- .given()
- .url(TestData.getMeUrl())
- .usernameAndPassword(newUser, password)
- .getUserResource()
- .assertStatusCode(403);
- }
-
-
-
}
diff --git a/scm-it/src/test/java/sonia/scm/it/utils/RegExMatcher.java b/scm-it/src/test/java/sonia/scm/it/utils/RegExMatcher.java
index 10386a682f..8fb9fdf798 100644
--- a/scm-it/src/test/java/sonia/scm/it/utils/RegExMatcher.java
+++ b/scm-it/src/test/java/sonia/scm/it/utils/RegExMatcher.java
@@ -24,6 +24,6 @@ public class RegExMatcher extends BaseMatcher {
@Override
public boolean matches(Object o) {
- return Pattern.compile(pattern).matcher(o.toString()).matches();
+ return o != null && Pattern.compile(pattern).matcher(o.toString()).matches();
}
}
diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigInIndexResource.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigInIndexResource.java
new file mode 100644
index 0000000000..a1120adda4
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/api/v2/resources/GitConfigInIndexResource.java
@@ -0,0 +1,40 @@
+package sonia.scm.api.v2.resources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import sonia.scm.config.ConfigurationPermissions;
+import sonia.scm.plugin.Extension;
+import sonia.scm.web.JsonEnricherBase;
+import sonia.scm.web.JsonEnricherContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import static java.util.Collections.singletonMap;
+import static sonia.scm.web.VndMediaType.INDEX;
+
+@Extension
+public class GitConfigInIndexResource extends JsonEnricherBase {
+
+ private final Provider scmPathInfoStore;
+
+ @Inject
+ public GitConfigInIndexResource(Provider scmPathInfoStore, ObjectMapper objectMapper) {
+ super(objectMapper);
+ this.scmPathInfoStore = scmPathInfoStore;
+ }
+
+ @Override
+ public void enrich(JsonEnricherContext context) {
+ if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.list().isPermitted()) {
+ String gitConfigUrl = new LinkBuilder(scmPathInfoStore.get().get(), GitConfigResource.class)
+ .method("get")
+ .parameters()
+ .href();
+
+ JsonNode gitConfigRefNode = createObject(singletonMap("href", value(gitConfigUrl)));
+
+ addPropertyNode(context.getResponseEntity().get("_links"), "gitConfig", gitConfigRefNode);
+ }
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigInIndexResourceTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigInIndexResourceTest.java
new file mode 100644
index 0000000000..665be19788
--- /dev/null
+++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/api/v2/resources/GitConfigInIndexResourceTest.java
@@ -0,0 +1,64 @@
+package sonia.scm.api.v2.resources;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.github.sdorra.shiro.ShiroRule;
+import com.github.sdorra.shiro.SubjectAware;
+import com.google.inject.util.Providers;
+import org.junit.Rule;
+import org.junit.Test;
+import sonia.scm.web.JsonEnricherContext;
+import sonia.scm.web.VndMediaType;
+
+import javax.ws.rs.core.MediaType;
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini")
+public class GitConfigInIndexResourceTest {
+
+ @Rule
+ public final ShiroRule shiroRule = new ShiroRule();
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private final ObjectNode root = objectMapper.createObjectNode();
+ private final GitConfigInIndexResource gitConfigInIndexResource;
+
+ public GitConfigInIndexResourceTest() {
+ root.put("_links", objectMapper.createObjectNode());
+ ScmPathInfoStore pathInfoStore = new ScmPathInfoStore();
+ pathInfoStore.set(() -> URI.create("/"));
+ gitConfigInIndexResource = new GitConfigInIndexResource(Providers.of(pathInfoStore), objectMapper);
+ }
+
+ @Test
+ @SubjectAware(username = "admin", password = "secret")
+ public void admin() {
+ JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
+
+ gitConfigInIndexResource.enrich(context);
+
+ assertEquals("/v2/config/git", root.get("_links").get("gitConfig").get("href").asText());
+ }
+
+ @Test
+ @SubjectAware(username = "readOnly", password = "secret")
+ public void user() {
+ JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
+
+ gitConfigInIndexResource.enrich(context);
+
+ assertFalse(root.get("_links").iterator().hasNext());
+ }
+
+ @Test
+ public void anonymous() {
+ JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
+
+ gitConfigInIndexResource.enrich(context);
+
+ assertFalse(root.get("_links").iterator().hasNext());
+ }
+}
diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini
index 36226edd7d..5d30a000f2 100644
--- a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini
+++ b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/configuration/shiro.ini
@@ -2,8 +2,10 @@
readOnly = secret, reader
writeOnly = secret, writer
readWrite = secret, readerWriter
+admin = secret, admin
[roles]
reader = configuration:read:git
writer = configuration:write:git
readerWriter = configuration:*:git
+admin = *
diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInIndexResource.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInIndexResource.java
new file mode 100644
index 0000000000..3de79b2f81
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/api/v2/resources/HgConfigInIndexResource.java
@@ -0,0 +1,40 @@
+package sonia.scm.api.v2.resources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import sonia.scm.config.ConfigurationPermissions;
+import sonia.scm.plugin.Extension;
+import sonia.scm.web.JsonEnricherBase;
+import sonia.scm.web.JsonEnricherContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import static java.util.Collections.singletonMap;
+import static sonia.scm.web.VndMediaType.INDEX;
+
+@Extension
+public class HgConfigInIndexResource extends JsonEnricherBase {
+
+ private final Provider scmPathInfoStore;
+
+ @Inject
+ public HgConfigInIndexResource(Provider scmPathInfoStore, ObjectMapper objectMapper) {
+ super(objectMapper);
+ this.scmPathInfoStore = scmPathInfoStore;
+ }
+
+ @Override
+ public void enrich(JsonEnricherContext context) {
+ if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.list().isPermitted()) {
+ String hgConfigUrl = new LinkBuilder(scmPathInfoStore.get().get(), HgConfigResource.class)
+ .method("get")
+ .parameters()
+ .href();
+
+ JsonNode hgConfigRefNode = createObject(singletonMap("href", value(hgConfigUrl)));
+
+ addPropertyNode(context.getResponseEntity().get("_links"), "hgConfig", hgConfigRefNode);
+ }
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInIndexResourceTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInIndexResourceTest.java
new file mode 100644
index 0000000000..27ab74932c
--- /dev/null
+++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/api/v2/resources/HgConfigInIndexResourceTest.java
@@ -0,0 +1,64 @@
+package sonia.scm.api.v2.resources;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.github.sdorra.shiro.ShiroRule;
+import com.github.sdorra.shiro.SubjectAware;
+import com.google.inject.util.Providers;
+import org.junit.Rule;
+import org.junit.Test;
+import sonia.scm.web.JsonEnricherContext;
+import sonia.scm.web.VndMediaType;
+
+import javax.ws.rs.core.MediaType;
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini")
+public class HgConfigInIndexResourceTest {
+
+ @Rule
+ public final ShiroRule shiroRule = new ShiroRule();
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private final ObjectNode root = objectMapper.createObjectNode();
+ private final HgConfigInIndexResource hgConfigInIndexResource;
+
+ public HgConfigInIndexResourceTest() {
+ root.put("_links", objectMapper.createObjectNode());
+ ScmPathInfoStore pathInfoStore = new ScmPathInfoStore();
+ pathInfoStore.set(() -> URI.create("/"));
+ hgConfigInIndexResource = new HgConfigInIndexResource(Providers.of(pathInfoStore), objectMapper);
+ }
+
+ @Test
+ @SubjectAware(username = "admin", password = "secret")
+ public void admin() {
+ JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
+
+ hgConfigInIndexResource.enrich(context);
+
+ assertEquals("/v2/config/hg", root.get("_links").get("hgConfig").get("href").asText());
+ }
+
+ @Test
+ @SubjectAware(username = "readOnly", password = "secret")
+ public void user() {
+ JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
+
+ hgConfigInIndexResource.enrich(context);
+
+ assertFalse(root.get("_links").iterator().hasNext());
+ }
+
+ @Test
+ public void anonymous() {
+ JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
+
+ hgConfigInIndexResource.enrich(context);
+
+ assertFalse(root.get("_links").iterator().hasNext());
+ }
+}
diff --git a/scm-plugins/scm-hg-plugin/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-plugins/scm-hg-plugin/src/test/resources/sonia/scm/configuration/shiro.ini
index fc08bb83ac..d8083a04c9 100644
--- a/scm-plugins/scm-hg-plugin/src/test/resources/sonia/scm/configuration/shiro.ini
+++ b/scm-plugins/scm-hg-plugin/src/test/resources/sonia/scm/configuration/shiro.ini
@@ -2,8 +2,10 @@
readOnly = secret, reader
writeOnly = secret, writer
readWrite = secret, readerWriter
+admin = secret, admin
[roles]
reader = configuration:read:hg
writer = configuration:write:hg
readerWriter = configuration:*:hg
+admin = *
diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigInIndexResource.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigInIndexResource.java
new file mode 100644
index 0000000000..5ee1de3169
--- /dev/null
+++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/api/v2/resources/SvnConfigInIndexResource.java
@@ -0,0 +1,40 @@
+package sonia.scm.api.v2.resources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import sonia.scm.config.ConfigurationPermissions;
+import sonia.scm.plugin.Extension;
+import sonia.scm.web.JsonEnricherBase;
+import sonia.scm.web.JsonEnricherContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import static java.util.Collections.singletonMap;
+import static sonia.scm.web.VndMediaType.INDEX;
+
+@Extension
+public class SvnConfigInIndexResource extends JsonEnricherBase {
+
+ private final Provider scmPathInfoStore;
+
+ @Inject
+ public SvnConfigInIndexResource(Provider scmPathInfoStore, ObjectMapper objectMapper) {
+ super(objectMapper);
+ this.scmPathInfoStore = scmPathInfoStore;
+ }
+
+ @Override
+ public void enrich(JsonEnricherContext context) {
+ if (resultHasMediaType(INDEX, context) && ConfigurationPermissions.list().isPermitted()) {
+ String svnConfigUrl = new LinkBuilder(scmPathInfoStore.get().get(), SvnConfigResource.class)
+ .method("get")
+ .parameters()
+ .href();
+
+ JsonNode svnConfigRefNode = createObject(singletonMap("href", value(svnConfigUrl)));
+
+ addPropertyNode(context.getResponseEntity().get("_links"), "svnConfig", svnConfigRefNode);
+ }
+ }
+}
diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigInIndexResourceTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigInIndexResourceTest.java
new file mode 100644
index 0000000000..8b87b57c6c
--- /dev/null
+++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/api/v2/resources/SvnConfigInIndexResourceTest.java
@@ -0,0 +1,64 @@
+package sonia.scm.api.v2.resources;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.github.sdorra.shiro.ShiroRule;
+import com.github.sdorra.shiro.SubjectAware;
+import com.google.inject.util.Providers;
+import org.junit.Rule;
+import org.junit.Test;
+import sonia.scm.web.JsonEnricherContext;
+import sonia.scm.web.VndMediaType;
+
+import javax.ws.rs.core.MediaType;
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini")
+public class SvnConfigInIndexResourceTest {
+
+ @Rule
+ public final ShiroRule shiroRule = new ShiroRule();
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private final ObjectNode root = objectMapper.createObjectNode();
+ private final SvnConfigInIndexResource svnConfigInIndexResource;
+
+ public SvnConfigInIndexResourceTest() {
+ root.put("_links", objectMapper.createObjectNode());
+ ScmPathInfoStore pathInfoStore = new ScmPathInfoStore();
+ pathInfoStore.set(() -> URI.create("/"));
+ svnConfigInIndexResource = new SvnConfigInIndexResource(Providers.of(pathInfoStore), objectMapper);
+ }
+
+ @Test
+ @SubjectAware(username = "admin", password = "secret")
+ public void admin() {
+ JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
+
+ svnConfigInIndexResource.enrich(context);
+
+ assertEquals("/v2/config/svn", root.get("_links").get("svnConfig").get("href").asText());
+ }
+
+ @Test
+ @SubjectAware(username = "readOnly", password = "secret")
+ public void user() {
+ JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
+
+ svnConfigInIndexResource.enrich(context);
+
+ assertFalse(root.get("_links").iterator().hasNext());
+ }
+
+ @Test
+ public void anonymous() {
+ JsonEnricherContext context = new JsonEnricherContext(URI.create("/index"), MediaType.valueOf(VndMediaType.INDEX), root);
+
+ svnConfigInIndexResource.enrich(context);
+
+ assertFalse(root.get("_links").iterator().hasNext());
+ }
+}
diff --git a/scm-plugins/scm-svn-plugin/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-plugins/scm-svn-plugin/src/test/resources/sonia/scm/configuration/shiro.ini
index 7e4233b540..fe84723e0a 100644
--- a/scm-plugins/scm-svn-plugin/src/test/resources/sonia/scm/configuration/shiro.ini
+++ b/scm-plugins/scm-svn-plugin/src/test/resources/sonia/scm/configuration/shiro.ini
@@ -2,8 +2,10 @@
readOnly = secret, reader
writeOnly = secret, writer
readWrite = secret, readerWriter
+admin = secret, admin
[roles]
reader = configuration:read:svn
writer = configuration:write:svn
readerWriter = configuration:*:svn
+admin = *
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java
new file mode 100644
index 0000000000..9346420f58
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDto.java
@@ -0,0 +1,16 @@
+package sonia.scm.api.v2.resources;
+
+import de.otto.edison.hal.HalRepresentation;
+import de.otto.edison.hal.Links;
+import lombok.Getter;
+
+@Getter
+public class IndexDto extends HalRepresentation {
+
+ private final String version;
+
+ IndexDto(String version, Links links) {
+ super(links);
+ this.version = version;
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java
new file mode 100644
index 0000000000..c8159f072c
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java
@@ -0,0 +1,50 @@
+package sonia.scm.api.v2.resources;
+
+import de.otto.edison.hal.Links;
+import org.apache.shiro.SecurityUtils;
+import sonia.scm.SCMContextProvider;
+import sonia.scm.config.ConfigurationPermissions;
+import sonia.scm.group.GroupPermissions;
+import sonia.scm.user.UserPermissions;
+
+import javax.inject.Inject;
+
+import static de.otto.edison.hal.Link.link;
+
+public class IndexDtoGenerator {
+
+ private final ResourceLinks resourceLinks;
+ private final SCMContextProvider scmContextProvider;
+
+ @Inject
+ public IndexDtoGenerator(ResourceLinks resourceLinks, SCMContextProvider scmContextProvider) {
+ this.resourceLinks = resourceLinks;
+ this.scmContextProvider = scmContextProvider;
+ }
+
+ public IndexDto generate() {
+ Links.Builder builder = Links.linkingTo();
+ builder.self(resourceLinks.index().self());
+ builder.single(link("uiPlugins", resourceLinks.uiPluginCollection().self()));
+ if (SecurityUtils.getSubject().isAuthenticated()) {
+ builder.single(
+ link("me", resourceLinks.me().self()),
+ link("logout", resourceLinks.authentication().logout())
+ );
+ if (UserPermissions.list().isPermitted()) {
+ builder.single(link("users", resourceLinks.userCollection().self()));
+ }
+ if (GroupPermissions.list().isPermitted()) {
+ builder.single(link("groups", resourceLinks.groupCollection().self()));
+ }
+ if (ConfigurationPermissions.list().isPermitted()) {
+ builder.single(link("config", resourceLinks.config().self()));
+ }
+ builder.single(link("repositories", resourceLinks.repositoryCollection().self()));
+ } else {
+ builder.single(link("login", resourceLinks.authentication().jsonLogin()));
+ }
+
+ return new IndexDto(scmContextProvider.getVersion(), builder.build());
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java
new file mode 100644
index 0000000000..088558c7dc
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexResource.java
@@ -0,0 +1,29 @@
+package sonia.scm.api.v2.resources;
+
+import com.webcohesion.enunciate.metadata.rs.TypeHint;
+import sonia.scm.web.VndMediaType;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+@Path(IndexResource.INDEX_PATH_V2)
+public class IndexResource {
+ public static final String INDEX_PATH_V2 = "v2/";
+
+ private final IndexDtoGenerator indexDtoGenerator;
+
+ @Inject
+ public IndexResource(IndexDtoGenerator indexDtoGenerator) {
+ this.indexDtoGenerator = indexDtoGenerator;
+ }
+
+ @GET
+ @Path("")
+ @Produces(VndMediaType.INDEX)
+ @TypeHint(IndexDto.class)
+ public IndexDto getIndex() {
+ return indexDtoGenerator.generate();
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java
index e978de443a..488577618c 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java
@@ -3,7 +3,6 @@ package sonia.scm.api.v2.resources;
import sonia.scm.repository.NamespaceAndName;
import javax.inject.Inject;
-import javax.ws.rs.core.UriInfo;
import java.net.URI;
class ResourceLinks {
@@ -441,7 +440,6 @@ class ResourceLinks {
}
}
-
public UIPluginLinks uiPlugin() {
return new UIPluginLinks(scmPathInfoStore.get());
}
@@ -473,4 +471,45 @@ class ResourceLinks {
return uiPluginCollectionLinkBuilder.method("plugins").parameters().method("getInstalledPlugins").parameters().href();
}
}
+
+ public AuthenticationLinks authentication() {
+ return new AuthenticationLinks(scmPathInfoStore.get());
+ }
+
+ static class AuthenticationLinks {
+ private final LinkBuilder loginLinkBuilder;
+
+ AuthenticationLinks(ScmPathInfo pathInfo) {
+ this.loginLinkBuilder = new LinkBuilder(pathInfo, AuthenticationResource.class);
+ }
+
+ String formLogin() {
+ return loginLinkBuilder.method("authenticateViaForm").parameters().href();
+ }
+
+ String jsonLogin() {
+ return loginLinkBuilder.method("authenticateViaJSONBody").parameters().href();
+ }
+
+ String logout() {
+ return loginLinkBuilder.method("logout").parameters().href();
+ }
+ }
+
+ public IndexLinks index() {
+ return new IndexLinks(scmPathInfoStore.get());
+ }
+
+ static class IndexLinks {
+ private final LinkBuilder indexLinkBuilder;
+
+ IndexLinks(ScmPathInfo pathInfo) {
+ indexLinkBuilder = new LinkBuilder(pathInfo, IndexResource.class);
+ }
+
+ String self() {
+ return indexLinkBuilder.method("getIndex").parameters().href();
+ }
+ }
+
}
diff --git a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java
index de0d689c52..d97a5b050e 100644
--- a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java
+++ b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java
@@ -84,7 +84,7 @@ public class SecurityFilter extends HttpFilter
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException
{
- if (!SecurityRequests.isAuthenticationRequest(request))
+ if (!SecurityRequests.isAuthenticationRequest(request) && !SecurityRequests.isIndexRequest(request))
{
Subject subject = SecurityUtils.getSubject();
if (hasPermission(subject))
diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java
index 1b9abb058b..bf46eb2a6f 100644
--- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java
+++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java
@@ -56,6 +56,7 @@ import sonia.scm.plugin.Extension;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.user.User;
+import sonia.scm.user.UserPermissions;
import sonia.scm.util.Util;
import java.util.List;
@@ -74,7 +75,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
// TODO move to util class
private static final String SEPARATOR = System.getProperty("line.separator", "\n");
-
+
/** Field description */
private static final String ADMIN_PERMISSION = "*";
@@ -88,7 +89,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
LoggerFactory.getLogger(DefaultAuthorizationCollector.class);
//~--- constructors ---------------------------------------------------------
-
+
/**
* Constructs ...
*
@@ -209,7 +210,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
String perm = permission.getType().getPermissionPrefix().concat(repository.getId());
if (logger.isTraceEnabled())
{
- logger.trace("add repository permission {} for user {} at repository {}",
+ logger.trace("add repository permission {} for user {} at repository {}",
perm, user.getName(), repository.getName());
}
@@ -254,6 +255,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
collectGlobalPermissions(builder, user, groups);
collectRepositoryPermissions(builder, user, groups);
+ builder.add(canReadOwnUser(user));
permissions = builder.build();
}
@@ -262,6 +264,10 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
return info;
}
+ private String canReadOwnUser(User user) {
+ return UserPermissions.read(user.getName()).asShiroString();
+ }
+
//~--- get methods ----------------------------------------------------------
private boolean isUserPermitted(User user, GroupNames groups,
@@ -272,7 +278,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|| ((!perm.isGroupPermission()) && user.getName().equals(perm.getName()));
//J+
}
-
+
@Subscribe
public void invalidateCache(AuthorizationChangedEvent event) {
if (event.isEveryUserAffected()) {
@@ -281,12 +287,12 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
invalidateCache();
}
}
-
+
private void invalidateUserCache(final String username) {
logger.info("invalidate cache for user {}, because of a received authorization event", username);
cache.removeAll((CacheKey item) -> username.equalsIgnoreCase(item.username));
}
-
+
private void invalidateCache() {
logger.info("invalidate cache, because of a received authorization event");
cache.clear();
diff --git a/scm-webapp/src/main/java/sonia/scm/security/SecurityRequests.java b/scm-webapp/src/main/java/sonia/scm/security/SecurityRequests.java
index 81bb2092c9..49d03f598b 100644
--- a/scm-webapp/src/main/java/sonia/scm/security/SecurityRequests.java
+++ b/scm-webapp/src/main/java/sonia/scm/security/SecurityRequests.java
@@ -11,6 +11,7 @@ import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
public final class SecurityRequests {
private static final Pattern URI_LOGIN_PATTERN = Pattern.compile(REST_API_PATH + "(?:/v2)?/auth/access_token");
+ private static final Pattern URI_INDEX_PATTERN = Pattern.compile(REST_API_PATH + "/v2/?");
private SecurityRequests() {}
@@ -23,4 +24,13 @@ public final class SecurityRequests {
return URI_LOGIN_PATTERN.matcher(uri).matches();
}
+ public static boolean isIndexRequest(HttpServletRequest request) {
+ String uri = request.getRequestURI().substring(request.getContextPath().length());
+ return isIndexRequest(uri);
+ }
+
+ public static boolean isIndexRequest(String uri) {
+ return URI_INDEX_PATTERN.matcher(uri).matches();
+ }
+
}
diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java
index d8fe469af9..c2444b43f5 100644
--- a/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java
+++ b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java
@@ -99,7 +99,7 @@ public class ApiAuthenticationFilter extends AuthenticationFilter
throws IOException, ServletException
{
// skip filter on login resource
- if (SecurityRequests.isAuthenticationRequest(request))
+ if (SecurityRequests.isAuthenticationRequest(request) )
{
chain.doFilter(request, response);
}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java
new file mode 100644
index 0000000000..83697a3c4a
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexResourceTest.java
@@ -0,0 +1,115 @@
+package sonia.scm.api.v2.resources;
+
+import com.github.sdorra.shiro.ShiroRule;
+import com.github.sdorra.shiro.SubjectAware;
+import org.assertj.core.api.Assertions;
+import org.junit.Rule;
+import org.junit.Test;
+import sonia.scm.SCMContextProvider;
+
+import java.net.URI;
+import java.util.Optional;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
+public class IndexResourceTest {
+
+ @Rule
+ public final ShiroRule shiroRule = new ShiroRule();
+
+ private final SCMContextProvider scmContextProvider = mock(SCMContextProvider.class);
+ private final IndexDtoGenerator indexDtoGenerator = new IndexDtoGenerator(ResourceLinksMock.createMock(URI.create("/")), scmContextProvider);
+ private final IndexResource indexResource = new IndexResource(indexDtoGenerator);
+
+ @Test
+ public void shouldRenderLoginUrlsForUnauthenticatedRequest() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("login")).matches(Optional::isPresent);
+ }
+
+ @Test
+ public void shouldRenderSelfLinkForUnauthenticatedRequest() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("self")).matches(Optional::isPresent);
+ }
+
+ @Test
+ public void shouldRenderUiPluginsLinkForUnauthenticatedRequest() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("uiPlugins")).matches(Optional::isPresent);
+ }
+
+ @Test
+ @SubjectAware(username = "trillian", password = "secret")
+ public void shouldRenderSelfLinkForAuthenticatedRequest() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("self")).matches(Optional::isPresent);
+ }
+
+ @Test
+ @SubjectAware(username = "trillian", password = "secret")
+ public void shouldRenderUiPluginsLinkForAuthenticatedRequest() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("uiPlugins")).matches(Optional::isPresent);
+ }
+
+ @Test
+ @SubjectAware(username = "trillian", password = "secret")
+ public void shouldRenderMeUrlForAuthenticatedRequest() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("me")).matches(Optional::isPresent);
+ }
+
+ @Test
+ @SubjectAware(username = "trillian", password = "secret")
+ public void shouldRenderLogoutUrlForAuthenticatedRequest() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("logout")).matches(Optional::isPresent);
+ }
+
+ @Test
+ @SubjectAware(username = "trillian", password = "secret")
+ public void shouldRenderRepositoriesForAuthenticatedRequest() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("repositories")).matches(Optional::isPresent);
+ }
+
+ @Test
+ @SubjectAware(username = "trillian", password = "secret")
+ public void shouldNotRenderAdminLinksIfNotAuthorized() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("users")).matches(o -> !o.isPresent());
+ Assertions.assertThat(index.getLinks().getLinkBy("groups")).matches(o -> !o.isPresent());
+ Assertions.assertThat(index.getLinks().getLinkBy("config")).matches(o -> !o.isPresent());
+ }
+
+ @Test
+ @SubjectAware(username = "dent", password = "secret")
+ public void shouldRenderAdminLinksIfAuthorized() {
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getLinks().getLinkBy("users")).matches(Optional::isPresent);
+ Assertions.assertThat(index.getLinks().getLinkBy("groups")).matches(Optional::isPresent);
+ Assertions.assertThat(index.getLinks().getLinkBy("config")).matches(Optional::isPresent);
+ }
+
+ @Test
+ public void shouldGenerateVersion() {
+ when(scmContextProvider.getVersion()).thenReturn("v1");
+
+ IndexDto index = indexResource.getIndex();
+
+ Assertions.assertThat(index.getVersion()).isEqualTo("v1");
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java
index c70510fe39..eed46a1f6e 100644
--- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java
+++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java
@@ -34,6 +34,8 @@ public class ResourceLinksMock {
when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo));
when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(uriInfo));
when(resourceLinks.uiPlugin()).thenReturn(new ResourceLinks.UIPluginLinks(uriInfo));
+ when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(uriInfo));
+ when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(uriInfo));
return resourceLinks;
}
diff --git a/scm-webapp/src/test/java/sonia/scm/it/UserPermissionITCase.java b/scm-webapp/src/test/java/sonia/scm/it/UserPermissionITCase.java
index dc960c1b42..3c0e2eed52 100644
--- a/scm-webapp/src/test/java/sonia/scm/it/UserPermissionITCase.java
+++ b/scm-webapp/src/test/java/sonia/scm/it/UserPermissionITCase.java
@@ -37,6 +37,7 @@ package sonia.scm.it;
import com.sun.jersey.api.client.ClientResponse;
import de.otto.edison.hal.HalRepresentation;
+import org.junit.Assume;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import sonia.scm.api.rest.ObjectMapperProvider;
@@ -158,6 +159,7 @@ public class UserPermissionITCase extends AbstractPermissionITCaseBase
@Override
protected void checkGetAllResponse(ClientResponse response)
{
+ Assume.assumeTrue(credentials.getUsername() == null);
if (!credentials.isAnonymous())
{
assertNotNull(response);
diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java
index ed5689c9ce..5e7963ef1d 100644
--- a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java
@@ -57,6 +57,7 @@ import sonia.scm.repository.RepositoryTestData;
import sonia.scm.user.User;
import sonia.scm.user.UserTestData;
+import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.nullValue;
@@ -160,7 +161,8 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.contains(Role.USER));
- assertThat(authInfo.getStringPermissions(), hasSize(0));
+ assertThat(authInfo.getStringPermissions(), hasSize(1));
+ assertThat(authInfo.getStringPermissions(), contains("user:read:trillian"));
assertThat(authInfo.getObjectPermissions(), nullValue());
}
@@ -207,7 +209,7 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
assertThat(authInfo.getObjectPermissions(), nullValue());
- assertThat(authInfo.getStringPermissions(), containsInAnyOrder("repository:read,pull:one", "repository:read,pull,push:two"));
+ assertThat(authInfo.getStringPermissions(), containsInAnyOrder("repository:read,pull:one", "repository:read,pull,push:two", "user:read:trillian"));
}
/**
@@ -228,7 +230,7 @@ public class DefaultAuthorizationCollectorTest {
AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
assertThat(authInfo.getObjectPermissions(), nullValue());
- assertThat(authInfo.getStringPermissions(), containsInAnyOrder("one:one", "two:two"));
+ assertThat(authInfo.getStringPermissions(), containsInAnyOrder("one:one", "two:two", "user:read:trillian"));
}
private void authenticate(User user, String group, String... groups) {