From 8c128127deef3b505eb621a8912773ec18eaae2f Mon Sep 17 00:00:00 2001 From: Mohamed Karray Date: Wed, 22 Aug 2018 09:18:17 +0200 Subject: [PATCH] #8771 implement integration tests --- .../java/sonia/scm/it/PermissionsITCase.java | 139 ++++++++++++++++++ .../java/sonia/scm/it/RepositoriesITCase.java | 60 +------- .../sonia/scm/it/RepositoryAccessITCase.java | 11 +- .../java/sonia/scm/it/RepositoryUtil.java | 72 +++++++-- .../src/test/java/sonia/scm/it/RestUtil.java | 18 ++- .../src/test/java/sonia/scm/it/TestData.java | 56 ++++++- .../DefaultAuthorizationCollector.java | 4 +- 7 files changed, 275 insertions(+), 85 deletions(-) create mode 100644 scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java diff --git a/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java new file mode 100644 index 0000000000..c372efaa2f --- /dev/null +++ b/scm-it/src/test/java/sonia/scm/it/PermissionsITCase.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + */ + + +package sonia.scm.it; + +import org.apache.http.HttpStatus; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import sonia.scm.repository.PermissionType; +import sonia.scm.repository.client.api.RepositoryClient; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import static org.junit.Assert.*; +import static sonia.scm.it.ScmTypes.availableScmTypes; + +@RunWith(Parameterized.class) +public class PermissionsITCase { + + public static final String USER_READ = "user_read"; + public static final String USER_PASS = "pass"; + private static final String USER_WRITE = "user_write"; + private static final String USER_OWNER = "user_owner"; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private final String repositoryType; + private int createdPermissions; + + + public PermissionsITCase(String repositoryType) { + this.repositoryType = repositoryType; + } + + @Parameters(name = "{0}") + public static Collection createParameters() { + return availableScmTypes(); + } + + @Before + public void prepareEnvironment() { + TestData.createDefault(); + TestData.createUser(USER_READ, USER_PASS); + TestData.createUserPermission(USER_READ, PermissionType.READ, repositoryType); + TestData.createUser(USER_WRITE, USER_PASS); + TestData.createUserPermission(USER_WRITE, PermissionType.WRITE, repositoryType); + TestData.createUser(USER_OWNER, USER_PASS); + TestData.createUserPermission(USER_OWNER, PermissionType.OWNER, repositoryType); + createdPermissions = 3; + } + + @Test + public void everyUserShouldSeePermissions() { + List userPermissions = TestData.getUserPermissions(USER_READ, USER_PASS, repositoryType); + assertEquals(userPermissions.size(), createdPermissions); + userPermissions = TestData.getUserPermissions(USER_WRITE, USER_PASS, repositoryType); + assertEquals(userPermissions.size(), createdPermissions); + userPermissions = TestData.getUserPermissions(USER_OWNER, USER_PASS, repositoryType); + assertEquals(userPermissions.size(), createdPermissions); + } + + @Test + public void everyUserShouldCloneRepository() throws IOException { + RepositoryClient client = RepositoryUtil.createRepositoryClient(repositoryType, temporaryFolder.newFolder(), USER_READ, USER_PASS); + assertEquals(1, Objects.requireNonNull(client.getWorkingCopy().list()).length); + client = RepositoryUtil.createRepositoryClient(repositoryType, temporaryFolder.newFolder(), USER_WRITE, USER_PASS); + assertEquals(1, Objects.requireNonNull(client.getWorkingCopy().list()).length); + client = RepositoryUtil.createRepositoryClient(repositoryType, temporaryFolder.newFolder(), USER_OWNER, USER_PASS); + assertEquals(1, Objects.requireNonNull(client.getWorkingCopy().list()).length); + } + + @Test + public void userWithREADPermissionShouldBeNotAuthorizedToCommit() throws IOException { + assertFalse(RepositoryUtil.canUserCommit(USER_READ, USER_PASS, repositoryType, temporaryFolder)); + } + + @Test + public void userWithOwnerPermissionShouldBeAuthorizedToCommit() throws IOException { + assertTrue(RepositoryUtil.canUserCommit(USER_OWNER, USER_PASS, repositoryType, temporaryFolder)); + } + + @Test + public void userWithWritePermissionShouldBeAuthorizedToCommit() throws IOException { + assertTrue(RepositoryUtil.canUserCommit(USER_WRITE, USER_PASS, repositoryType, temporaryFolder)); + } + + @Test + public void userWithWOwnerPermissionShouldBeAuthorizedToDeleteRepository(){ + RepositoryUtil.assertDeleteRepositoryOperation(USER_OWNER, HttpStatus.SC_NO_CONTENT, HttpStatus.SC_NOT_FOUND, USER_PASS, repositoryType); + } + + @Test + public void userWithWReadPermissionShouldNotBeAuthorizedToDeleteRepository(){ + RepositoryUtil.assertDeleteRepositoryOperation(USER_READ, HttpStatus.SC_FORBIDDEN, HttpStatus.SC_OK, USER_PASS, repositoryType); + } + + @Test + public void userWithWWritePermissionShouldNotBeAuthorizedToDeleteRepository(){ + RepositoryUtil.assertDeleteRepositoryOperation(USER_WRITE, HttpStatus.SC_FORBIDDEN, HttpStatus.SC_OK, USER_PASS, repositoryType); + } + +} diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java index cd791cb013..e609b0075e 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoriesITCase.java @@ -41,22 +41,18 @@ import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import sonia.scm.repository.Person; -import sonia.scm.repository.client.api.ClientCommand; import sonia.scm.repository.client.api.RepositoryClient; -import sonia.scm.repository.client.api.RepositoryClientFactory; import sonia.scm.web.VndMediaType; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.util.Collection; -import java.util.UUID; +import java.util.Objects; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static sonia.scm.it.RegExMatcher.matchesPattern; import static sonia.scm.it.RestUtil.createResourceUrl; import static sonia.scm.it.RestUtil.given; @@ -66,8 +62,6 @@ import static sonia.scm.it.TestData.repositoryJson; @RunWith(Parameterized.class) public class RepositoriesITCase { - public static final Person AUTHOR = new Person("SCM Administrator", "scmadmin@scm-manager.org"); - @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -142,57 +136,13 @@ public class RepositoriesITCase { @Test public void shouldCloneRepository() throws IOException { - RepositoryClient client = createRepositoryClient(); - assertEquals("expected metadata dir", 1, client.getWorkingCopy().list().length); + RepositoryClient client = RepositoryUtil.createRepositoryClient(repositoryType,temporaryFolder.getRoot()); + assertEquals("expected metadata dir", 1, Objects.requireNonNull(client.getWorkingCopy().list()).length); } @Test public void shouldCommitFiles() throws IOException { - RepositoryClient client = createRepositoryClient(); - - for (int i = 0; i < 5; i++) { - createRandomFile(client); - } - - commit(client); - - RepositoryClient checkClient = createRepositoryClient(); - assertEquals("expected 5 files and metadata dir", 6, checkClient.getWorkingCopy().list().length); + assertTrue(RepositoryUtil.canScmAdminCommit(repositoryType,temporaryFolder)); } - private static void createRandomFile(RepositoryClient client) throws IOException { - String uuid = UUID.randomUUID().toString(); - String name = "file-" + uuid + ".uuid"; - - File file = new File(client.getWorkingCopy(), name); - try (FileOutputStream out = new FileOutputStream(file)) { - out.write(uuid.getBytes()); - } - - client.getAddCommand().add(name); - } - - private static void commit(RepositoryClient repositoryClient) throws IOException { - repositoryClient.getCommitCommand().commit(AUTHOR, "commit"); - if ( repositoryClient.isCommandSupported(ClientCommand.PUSH) ) { - repositoryClient.getPushCommand().push(); - } - } - - private RepositoryClient createRepositoryClient() throws IOException { - RepositoryClientFactory clientFactory = new RepositoryClientFactory(); - String cloneUrl = readCloneUrl(); - return clientFactory.create(repositoryType, cloneUrl, "scmadmin", "scmadmin", temporaryFolder.newFolder()); - } - - private String readCloneUrl() { - return given(VndMediaType.REPOSITORY) - - .when() - .get(repositoryUrl) - - .then() - .extract() - .path("_links.httpProtocol.href"); - } } diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java index a461e40dea..ff8a3092a7 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryAccessITCase.java @@ -7,7 +7,9 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import sonia.scm.repository.client.api.RepositoryClient; +import java.io.File; import java.io.IOException; import java.util.Collection; @@ -23,7 +25,7 @@ public class RepositoryAccessITCase { public TemporaryFolder tempFolder = new TemporaryFolder(); private final String repositoryType; - private RepositoryUtil repositoryUtil; + private File folder; public RepositoryAccessITCase(String repositoryType) { this.repositoryType = repositoryType; @@ -35,16 +37,17 @@ public class RepositoryAccessITCase { } @Before - public void initClient() throws IOException { + public void initClient() { TestData.createDefault(); - repositoryUtil = new RepositoryUtil(repositoryType, tempFolder.getRoot()); + folder = tempFolder.getRoot(); } @Test public void shouldFindBranches() throws IOException { assumeFalse("There are no branches for SVN", repositoryType.equals("svn")); - repositoryUtil.createAndCommitFile("a.txt", "a"); + RepositoryClient repositoryClient = RepositoryUtil.createRepositoryClient(repositoryType, folder ); + RepositoryUtil.createAndCommitFile(folder, repositoryClient, "scmadmin", "a.txt", "a"); String branchesUrl = given() .when() diff --git a/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java b/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java index 98d4c8cdab..0c59bdc5ad 100644 --- a/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java +++ b/scm-it/src/test/java/sonia/scm/it/RepositoryUtil.java @@ -3,34 +3,61 @@ package sonia.scm.it; import com.google.common.base.Charsets; import com.google.common.io.Files; import org.apache.http.HttpStatus; +import org.junit.rules.TemporaryFolder; import sonia.scm.repository.Changeset; import sonia.scm.repository.Person; import sonia.scm.repository.client.api.ClientCommand; import sonia.scm.repository.client.api.RepositoryClient; +import sonia.scm.repository.client.api.RepositoryClientException; import sonia.scm.repository.client.api.RepositoryClientFactory; import sonia.scm.web.VndMediaType; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.util.UUID; -import static sonia.scm.it.RestUtil.ADMIN_PASSWORD; -import static sonia.scm.it.RestUtil.ADMIN_USERNAME; import static sonia.scm.it.RestUtil.given; public class RepositoryUtil { private static final RepositoryClientFactory REPOSITORY_CLIENT_FACTORY = new RepositoryClientFactory(); - private final RepositoryClient repositoryClient; - private final File folder; + static void addRandomFileToRepository(RepositoryClient client) throws IOException { + String uuid = UUID.randomUUID().toString(); + String name = "file-" + uuid + ".uuid"; - RepositoryUtil(String repositoryType, File folder) throws IOException { - this.repositoryClient = createRepositoryClient(repositoryType, folder); - this.folder = folder; + File file = new File(client.getWorkingCopy(), name); + try (FileOutputStream out = new FileOutputStream(file)) { + out.write(uuid.getBytes()); + } + client.getAddCommand().add(name); + } + + static boolean canScmAdminCommit(String repositoryType, TemporaryFolder temporaryFolder) throws IOException { + return canUserCommit("scmadmin", "scmadmin", repositoryType, temporaryFolder); + } + + static boolean canUserCommit(String username, String password, String repositoryType, TemporaryFolder temporaryFolder) throws IOException { + RepositoryClient client = RepositoryUtil.createRepositoryClient(repositoryType, temporaryFolder.newFolder(), username, password); + for (int i = 0; i < 5; i++) { + addRandomFileToRepository(client); + } + try{ + commit(client, username, "commit"); + }catch (RepositoryClientException e){ + return false; + } + RepositoryClient checkClient = RepositoryUtil.createRepositoryClient(repositoryType, temporaryFolder.newFolder(), username, password); + return checkClient.getWorkingCopy().list().length == 6; } static RepositoryClient createRepositoryClient(String repositoryType, File folder) throws IOException { - String httpProtocolUrl = given(VndMediaType.REPOSITORY) + return createRepositoryClient(repositoryType, folder, "scmadmin", "scmadmin"); + } + + static RepositoryClient createRepositoryClient(String repositoryType, File folder, String username, String password) throws IOException { + String httpProtocolUrl = given(VndMediaType.REPOSITORY, username, password) .when() .get(TestData.getDefaultRepositoryUrl(repositoryType)) @@ -40,20 +67,33 @@ public class RepositoryUtil { .extract() .path("_links.httpProtocol.href"); - - return REPOSITORY_CLIENT_FACTORY.create(repositoryType, httpProtocolUrl, ADMIN_USERNAME, ADMIN_PASSWORD, folder); + return REPOSITORY_CLIENT_FACTORY.create(repositoryType, httpProtocolUrl, username, password, folder); } + static void assertDeleteRepositoryOperation(String user, int deleteStatus, int getStatus, String password, String repositoryType) { + given(VndMediaType.REPOSITORY, user, password) - void createAndCommitFile(String fileName, String content) throws IOException { + .when() + .delete(TestData.getDefaultRepositoryUrl(repositoryType)) + + .then() + .statusCode(deleteStatus); + + given(VndMediaType.REPOSITORY, user, password) + + .when() + .get(TestData.getDefaultRepositoryUrl(repositoryType)) + + .then() + .statusCode(getStatus); + } + static void createAndCommitFile(File folder, RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException { Files.write(content, new File(folder, fileName), Charsets.UTF_8); repositoryClient.getAddCommand().add(fileName); - commit("added " + fileName); + commit(repositoryClient, username, "added " + fileName); } - Changeset commit(String message) throws IOException { - Changeset changeset = repositoryClient.getCommitCommand().commit( - new Person("scmadmin", "scmadmin@scm-manager.org"), message - ); + static Changeset commit(RepositoryClient repositoryClient, String username, String message) throws IOException { + Changeset changeset = repositoryClient.getCommitCommand().commit(new Person(username, username + "@scm-manager.org"), message); if (repositoryClient.isCommandSupported(ClientCommand.PUSH)) { repositoryClient.getPushCommand().push(); } diff --git a/scm-it/src/test/java/sonia/scm/it/RestUtil.java b/scm-it/src/test/java/sonia/scm/it/RestUtil.java index 76d2461637..a7409e1995 100644 --- a/scm-it/src/test/java/sonia/scm/it/RestUtil.java +++ b/scm-it/src/test/java/sonia/scm/it/RestUtil.java @@ -19,15 +19,19 @@ public class RestUtil { public static final String ADMIN_USERNAME = "scmadmin"; public static final String ADMIN_PASSWORD = "scmadmin"; - public static RequestSpecification given(String mediaType) { - return RestAssured.given() - .contentType(mediaType) - .accept(mediaType) - .auth().preemptive().basic(ADMIN_USERNAME, ADMIN_PASSWORD); - } - public static RequestSpecification given() { return RestAssured.given() .auth().preemptive().basic(ADMIN_USERNAME, ADMIN_PASSWORD); } + + public static RequestSpecification given(String mediaType) { + return given(mediaType, ADMIN_USERNAME, ADMIN_PASSWORD); + } + + public static RequestSpecification given(String mediaType, String username, String password) { + return RestAssured.given() + .contentType(mediaType) + .accept(mediaType) + .auth().preemptive().basic(username, password); + } } diff --git a/scm-it/src/test/java/sonia/scm/it/TestData.java b/scm-it/src/test/java/sonia/scm/it/TestData.java index b2785b2051..e9177807eb 100644 --- a/scm-it/src/test/java/sonia/scm/it/TestData.java +++ b/scm-it/src/test/java/sonia/scm/it/TestData.java @@ -3,6 +3,7 @@ package sonia.scm.it; import org.apache.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.repository.PermissionType; import sonia.scm.web.VndMediaType; import javax.json.Json; @@ -19,7 +20,9 @@ public class TestData { private static final Logger LOG = LoggerFactory.getLogger(TestData.class); - private static final List PROTECTED_USERS = asList("scmadmin", "anonymous"); + public static final String USER_SCM_ADMIN = "scmadmin"; + public static final String USER_ANONYMOUS = "anonymous"; +private static final List PROTECTED_USERS = asList(USER_SCM_ADMIN, USER_ANONYMOUS); private static Map DEFAULT_REPOSITORIES = new HashMap<>(); @@ -38,6 +41,57 @@ public class TestData { return DEFAULT_REPOSITORIES.get(repositoryType); } + public static void createUser(String username, String password) { + given(VndMediaType.USER) + .when() + .content(" {\n" + + " \"active\": true,\n" + + " \"admin\": false,\n" + + " \"creationDate\": \"2018-08-21T12:26:46.084Z\",\n" + + " \"displayName\": \""+username+"\",\n" + + " \"mail\": \"user1@scm-manager.org\",\n" + + " \"name\": \"" + username + "\",\n" + + " \"password\": \"" + password + "\",\n" + + " \"type\": \"xml\"\n" + + " \n" + + " }") + .post(createResourceUrl("users")) + .then() + .statusCode(HttpStatus.SC_CREATED) + ; + } + + + public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) { + given(VndMediaType.PERMISSION) + .when() + .content("{\n" + + "\t\"type\": \""+permissionType.name()+"\",\n" + + "\t\"name\": \""+name+"\",\n" + + "\t\"groupPermission\": false\n" + + "\t\n" + + "}") + .post(TestData.getDefaultPermissionUrl(repositoryType)) + .then() + .statusCode(HttpStatus.SC_CREATED) + ; + } + + public static List getUserPermissions(String username, String password, String repositoryType) { + return given(VndMediaType.PERMISSION, username, password) + .when() + .get(TestData.getDefaultPermissionUrl(repositoryType)) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .body().jsonPath().getList(""); + } + + private static String getDefaultPermissionUrl(String repositoryType) { + return getDefaultRepositoryUrl(repositoryType)+"/permissions/"; + } + + private static void cleanupRepositories() { List repositories = given(VndMediaType.REPOSITORY_COLLECTION) .when() 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 d868b81499..e6a2502997 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java @@ -207,9 +207,9 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector boolean hasPermission = false; for (sonia.scm.repository.Permission permission : repositoryPermissions) { - if (isUserPermitted(user, groups, permission)) + hasPermission = isUserPermitted(user, groups, permission); + if (hasPermission) { - String perm = permission.getType().getPermissionPrefix().concat(repository.getId()); if (logger.isTraceEnabled()) {