mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 23:45:44 +01:00
merge
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -188,15 +188,14 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- TODO replace by proper version from maven central (group: com.github.sdorra) once its there. -->
|
||||
<dependency>
|
||||
<groupId>com.github.sdorra.shiro-static-permissions</groupId>
|
||||
<groupId>com.github.sdorra</groupId>
|
||||
<artifactId>ssp-lib</artifactId>
|
||||
<version>${ssp.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.sdorra.shiro-static-permissions</groupId>
|
||||
<groupId>com.github.sdorra</groupId>
|
||||
<artifactId>ssp-processor</artifactId>
|
||||
<version>${ssp.version}</version>
|
||||
<optional>true</optional>
|
||||
@@ -765,7 +764,7 @@
|
||||
<jetty.maven.version>9.2.10.v20150310</jetty.maven.version>
|
||||
|
||||
<!-- security libraries -->
|
||||
<ssp.version>967c8fd521</ssp.version>
|
||||
<ssp.version>1.1.0</ssp.version>
|
||||
<shiro.version>1.4.0</shiro.version>
|
||||
|
||||
<!-- repostitory libraries -->
|
||||
|
||||
@@ -94,6 +94,12 @@
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- json -->
|
||||
|
||||
<dependency>
|
||||
@@ -160,14 +166,13 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- TODO replace by proper version from maven central (group: com.github.sdorra) once its there. -->
|
||||
<dependency>
|
||||
<groupId>com.github.sdorra.shiro-static-permissions</groupId>
|
||||
<groupId>com.github.sdorra</groupId>
|
||||
<artifactId>ssp-lib</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.sdorra.shiro-static-permissions</groupId>
|
||||
<groupId>com.github.sdorra</groupId>
|
||||
<artifactId>ssp-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.github.sdorra.ssp.StaticPermissions;
|
||||
@StaticPermissions(
|
||||
value = "configuration",
|
||||
permissions = {"read", "write"},
|
||||
globalPermissions = {}
|
||||
globalPermissions = {"list"}
|
||||
)
|
||||
public interface Configuration extends PermissionObject {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
40
scm-core/src/main/java/sonia/scm/web/JsonEnricherBase.java
Normal file
40
scm-core/src/main/java/sonia/scm/web/JsonEnricherBase.java
Normal file
@@ -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<String, Object> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
48
scm-it/src/test/java/sonia/scm/it/IndexITCase.java
Normal file
48
scm-it/src/test/java/sonia/scm/it/IndexITCase.java
Normal file
@@ -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/.+")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@ public class RegExMatcher extends BaseMatcher<String> {
|
||||
|
||||
@Override
|
||||
public boolean matches(Object o) {
|
||||
return Pattern.compile(pattern).matcher(o.toString()).matches();
|
||||
return o != null && Pattern.compile(pattern).matcher(o.toString()).matches();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> scmPathInfoStore;
|
||||
|
||||
@Inject
|
||||
public GitConfigInIndexResource(Provider<ScmPathInfoStore> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 = *
|
||||
|
||||
@@ -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> scmPathInfoStore;
|
||||
|
||||
@Inject
|
||||
public HgConfigInIndexResource(Provider<ScmPathInfoStore> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 = *
|
||||
|
||||
@@ -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> scmPathInfoStore;
|
||||
|
||||
@Inject
|
||||
public SvnConfigInIndexResource(Provider<ScmPathInfoStore> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 = *
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package sonia.scm.api.v2;
|
||||
|
||||
public final class ValidationConstraints {
|
||||
|
||||
private ValidationConstraints() {}
|
||||
|
||||
/**
|
||||
* A user or group name should not start with <code>@</code> or a whitespace
|
||||
* and it not contains whitespaces
|
||||
* and the characters: . - _ @ are allowed
|
||||
*/
|
||||
public static final String USER_GROUP_PATTERN = "^[A-Za-z0-9\\.\\-_][A-Za-z0-9\\.\\-_@]*$";
|
||||
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
|
||||
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
public class GroupDto extends HalRepresentation {
|
||||
|
||||
@@ -20,7 +22,7 @@ public class GroupDto extends HalRepresentation {
|
||||
private String description;
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private Instant lastModified;
|
||||
@Pattern(regexp = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$")
|
||||
@Pattern(regexp = USER_GROUP_PATTERN)
|
||||
private String name;
|
||||
@NotEmpty
|
||||
private String type;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,20 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter @Setter @ToString
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
|
||||
|
||||
@Getter @Setter @ToString @NoArgsConstructor
|
||||
public class PermissionDto extends HalRepresentation {
|
||||
|
||||
public static final String GROUP_PREFIX = "@";
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Pattern(regexp = USER_GROUP_PATTERN)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
@@ -28,9 +33,6 @@ public class PermissionDto extends HalRepresentation {
|
||||
|
||||
private boolean groupPermission = false;
|
||||
|
||||
public PermissionDto() {
|
||||
}
|
||||
|
||||
public PermissionDto(String permissionName, boolean groupPermission) {
|
||||
name = permissionName;
|
||||
this.groupPermission = groupPermission;
|
||||
|
||||
@@ -16,6 +16,7 @@ import sonia.scm.repository.RepositoryPermissions;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
@@ -70,7 +71,7 @@ public class PermissionRootResource {
|
||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||
@Consumes(VndMediaType.PERMISSION)
|
||||
@Path("")
|
||||
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name, PermissionDto permission) throws Exception {
|
||||
public Response create(@PathParam("namespace") String namespace, @PathParam("name") String name,@Valid PermissionDto permission) throws AlreadyExistsException, NotFoundException {
|
||||
log.info("try to add new permission: {}", permission);
|
||||
Repository repository = load(namespace, name);
|
||||
RepositoryPermissions.permissionWrite(repository).check();
|
||||
@@ -156,7 +157,7 @@ public class PermissionRootResource {
|
||||
public Response update(@PathParam("namespace") String namespace,
|
||||
@PathParam("name") String name,
|
||||
@PathParam("permission-name") String permissionName,
|
||||
PermissionDto permission) throws NotFoundException, AlreadyExistsException {
|
||||
@Valid PermissionDto permission) throws NotFoundException, AlreadyExistsException {
|
||||
log.info("try to update the permission with name: {}. the modified permission is: {}", permissionName, permission);
|
||||
Repository repository = load(namespace, name);
|
||||
RepositoryPermissions.permissionWrite(repository).check();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import javax.validation.constraints.Pattern;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
|
||||
|
||||
@NoArgsConstructor @Getter @Setter
|
||||
public class UserDto extends HalRepresentation {
|
||||
private boolean active;
|
||||
@@ -24,7 +26,7 @@ public class UserDto extends HalRepresentation {
|
||||
private Instant lastModified;
|
||||
@NotEmpty @Email
|
||||
private String mail;
|
||||
@Pattern(regexp = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$")
|
||||
@Pattern(regexp = USER_GROUP_PATTERN)
|
||||
private String name;
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String password;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package sonia.scm.api.v2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static sonia.scm.api.v2.ValidationConstraints.USER_GROUP_PATTERN;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class ValidationConstraints_IllegalCharactersTest {
|
||||
|
||||
private static final List<Character> ACCEPTED_CHARS = asList('@', '_', '-', '.');
|
||||
|
||||
private final Pattern userGroupPattern=Pattern.compile(USER_GROUP_PATTERN);
|
||||
|
||||
private final String expression;
|
||||
|
||||
public ValidationConstraints_IllegalCharactersTest(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
public static Collection<String[]> createParameters() {
|
||||
return Stream.concat(IntStream.range(0x20, 0x2f).mapToObj(i -> (char) i), // chars before '0'
|
||||
Stream.concat(IntStream.range(0x3a, 0x40).mapToObj(i -> (char) i), // chars between '9' and 'A'
|
||||
Stream.concat(IntStream.range(0x5b, 0x60).mapToObj(i -> (char) i), // chars between 'Z' and 'a'
|
||||
IntStream.range(0x7b, 0xff).mapToObj(i -> (char) i)))) // chars after 'z'
|
||||
.filter(c -> !ACCEPTED_CHARS.contains(c))
|
||||
.flatMap(c -> Stream.of("abc" + c + "xyz", "@" + c, c + "tail"))
|
||||
.map(c -> new String[] {c})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotAcceptSpecialCharacters() {
|
||||
assertFalse(userGroupPattern.matcher(expression).matches());
|
||||
}
|
||||
}
|
||||
@@ -224,6 +224,64 @@ public class GroupRootResourceTest {
|
||||
assertEquals("user1", createdGroup.getMembers().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGet400OnCreatingNewGroupWithNotAllowedCharacters() throws URISyntaxException {
|
||||
// the @ character at the begin of the name is not allowed
|
||||
String groupJson = "{ \"name\": \"@grpname\", \"type\": \"admin\" }";
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.post("/" + GroupRootResource.GROUPS_PATH_V2)
|
||||
.contentType(VndMediaType.GROUP)
|
||||
.content(groupJson.getBytes());
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
|
||||
// the whitespace at the begin of the name is not allowed
|
||||
groupJson = "{ \"name\": \" grpname\", \"type\": \"admin\" }";
|
||||
request = MockHttpRequest
|
||||
.post("/" + GroupRootResource.GROUPS_PATH_V2)
|
||||
.contentType(VndMediaType.GROUP)
|
||||
.content(groupJson.getBytes());
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
|
||||
// the characters {[ are not allowed
|
||||
groupJson = "{ \"name\": \"grp{name}\", \"type\": \"admin\" }";
|
||||
request = MockHttpRequest
|
||||
.post("/" + GroupRootResource.GROUPS_PATH_V2)
|
||||
.contentType(VndMediaType.GROUP)
|
||||
.content(groupJson.getBytes());
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
|
||||
groupJson = "{ \"name\": \"grp[name]\", \"type\": \"admin\" }";
|
||||
request = MockHttpRequest
|
||||
.post("/" + GroupRootResource.GROUPS_PATH_V2)
|
||||
.contentType(VndMediaType.GROUP)
|
||||
.content(groupJson.getBytes());
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
|
||||
groupJson = "{ \"name\": \"grp/name\", \"type\": \"admin\" }";
|
||||
request = MockHttpRequest
|
||||
.post("/" + GroupRootResource.GROUPS_PATH_V2)
|
||||
.contentType(VndMediaType.GROUP)
|
||||
.content(groupJson.getBytes());
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFailForMissingContent() throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import java.util.stream.Stream;
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
import static org.mockito.Matchers.any;
|
||||
@@ -233,6 +234,35 @@ public class PermissionRootResourceTest extends RepositoryTestBase {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldGet400OnCreatingNewPermissionWithNotAllowedCharacters() throws URISyntaxException {
|
||||
// the @ character at the begin of the name is not allowed
|
||||
createUserWithRepository("user");
|
||||
String permissionJson = "{ \"name\": \"@permission\", \"type\": \"OWNER\" }";
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
||||
.content(permissionJson.getBytes())
|
||||
.contentType(VndMediaType.PERMISSION);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
|
||||
// the whitespace at the begin opf the name is not allowed
|
||||
permissionJson = "{ \"name\": \" permission\", \"type\": \"OWNER\" }";
|
||||
request = MockHttpRequest
|
||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
|
||||
.content(permissionJson.getBytes())
|
||||
.contentType(VndMediaType.PERMISSION);
|
||||
response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetCreatedPermissions() throws URISyntaxException {
|
||||
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_WRITE);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,32 @@ public class UserRootResourceTest {
|
||||
assertTrue(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/Neo\"}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGet400OnCreatingNewUserWithNotAllowedCharacters() throws URISyntaxException {
|
||||
// the @ character at the begin of the name is not allowed
|
||||
String userJson = "{ \"name\": \"@user\", \"type\": \"db\" }";
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.post("/" + UserRootResource.USERS_PATH_V2)
|
||||
.contentType(VndMediaType.USER)
|
||||
.content(userJson.getBytes());
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
|
||||
// the whitespace at the begin opf the name is not allowed
|
||||
userJson = "{ \"name\": \" user\", \"type\": \"db\" }";
|
||||
request = MockHttpRequest
|
||||
.post("/" + UserRootResource.USERS_PATH_V2)
|
||||
.contentType(VndMediaType.USER)
|
||||
.content(userJson.getBytes());
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "unpriv")
|
||||
public void shouldCreateLimitedResponseForSimpleUser() throws URISyntaxException {
|
||||
|
||||
@@ -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<User>
|
||||
@Override
|
||||
protected void checkGetAllResponse(ClientResponse response)
|
||||
{
|
||||
Assume.assumeTrue(credentials.getUsername() == null);
|
||||
if (!credentials.isAnonymous())
|
||||
{
|
||||
assertNotNull(response);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user