Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2019-05-20 08:51:59 +02:00
91 changed files with 5387 additions and 547 deletions

View File

@@ -2,8 +2,6 @@ package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.collect.ImmutableList;
import com.google.inject.util.Providers;
import de.otto.edison.hal.HalRepresentation;
@@ -21,7 +19,6 @@ import org.jboss.resteasy.mock.MockHttpResponse;
import org.jboss.resteasy.spi.HttpRequest;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -35,6 +32,7 @@ import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.web.VndMediaType;
import javax.ws.rs.HttpMethod;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
@@ -64,11 +62,6 @@ import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
import static sonia.scm.api.v2.resources.RepositoryPermissionDto.GROUP_PREFIX;
@Slf4j
@SubjectAware(
username = "trillian",
password = "secret",
configuration = "classpath:sonia/scm/repository/shiro.ini"
)
public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
private static final String REPOSITORY_NAMESPACE = "repo_namespace";
private static final String REPOSITORY_NAME = "repo";
@@ -114,9 +107,6 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
private Dispatcher dispatcher;
@Rule
public ShiroRule shiro = new ShiroRule();
@Mock
private RepositoryManager repositoryManager;
@@ -363,6 +353,69 @@ public class RepositoryPermissionRootResourceTest extends RepositoryTestBase {
assertGettingExpectedPermissions(expectedPermissions, PERMISSION_READ);
}
@Test
public void shouldCreateValidationErrorForMissingRoleAndEmptyVerbs() throws Exception {
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
MockHttpResponse response = new MockHttpResponse();
HttpRequest request = MockHttpRequest
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
.content("{ 'name' : 'permission_name', 'verbs' : [] }".replaceAll("'", "\"").getBytes())
.contentType(VndMediaType.REPOSITORY_PERMISSION);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.getContentAsString()).contains("permission must either have a role or a not empty set of verbs");
}
@Test
public void shouldCreateValidationErrorForEmptyRoleAndEmptyVerbs() throws Exception {
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
MockHttpResponse response = new MockHttpResponse();
HttpRequest request = MockHttpRequest
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
.content("{ 'name' : 'permission_name', 'role': '', 'verbs' : [] }".replaceAll("'", "\"").getBytes())
.contentType(VndMediaType.REPOSITORY_PERMISSION);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.getContentAsString()).contains("permission must either have a role or a not empty set of verbs");
}
@Test
public void shouldCreateValidationErrorForRoleAndVerbs() throws Exception {
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
MockHttpResponse response = new MockHttpResponse();
HttpRequest request = MockHttpRequest
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
.content("{ 'name' : 'permission_name', 'role': 'some role', 'verbs' : ['read'] }".replaceAll("'", "\"").getBytes())
.contentType(VndMediaType.REPOSITORY_PERMISSION);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.getContentAsString()).contains("permission must either have a role or a not empty set of verbs");
}
@Test
public void shouldPassWithoutValidationErrorForRoleAndEmptyVerbs() throws Exception {
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
MockHttpResponse response = new MockHttpResponse();
HttpRequest request = MockHttpRequest
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
.content("{ 'name' : 'permission_name', 'role': 'some role', 'verbs': [] }".replaceAll("'", "\"").getBytes())
.contentType(VndMediaType.REPOSITORY_PERMISSION);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(201);
}
@Test
public void shouldPassWithoutValidationErrorForRoleAndNoVerbs() throws Exception {
createUserWithRepositoryAndPermissions(TEST_PERMISSIONS, PERMISSION_OWNER);
MockHttpResponse response = new MockHttpResponse();
HttpRequest request = MockHttpRequest
.create(HttpMethod.POST, "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + PATH_OF_ALL_PERMISSIONS)
.content("{ 'name' : 'permission_name', 'role': 'some role' }".replaceAll("'", "\"").getBytes())
.contentType(VndMediaType.REPOSITORY_PERMISSION);
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(201);
}
private void assertGettingExpectedPermissions(ImmutableList<RepositoryPermission> expectedPermissions, String userPermission) throws URISyntaxException {
assertExpectedRequest(requestGETAllPermissions
.expectedResponseStatus(200)

View File

@@ -0,0 +1,317 @@
package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.inject.util.Providers;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.PageResult;
import sonia.scm.api.rest.JSONContextResolver;
import sonia.scm.api.rest.ObjectMapperProvider;
import sonia.scm.repository.RepositoryRole;
import sonia.scm.repository.RepositoryRoleManager;
import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.util.Collections;
import static java.net.URI.create;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
@SubjectAware(
username = "trillian",
password = "secret",
configuration = "classpath:sonia/scm/repository/shiro.ini"
)
@RunWith(MockitoJUnitRunner.Silent.class)
public class RepositoryRoleRootResourceTest {
public static final String CUSTOM_ROLE = "customRole";
public static final String SYSTEM_ROLE = "systemRole";
public static final RepositoryRole CUSTOM_REPOSITORY_ROLE = new RepositoryRole(CUSTOM_ROLE, Collections.singleton("verb"), "xml");
public static final RepositoryRole SYSTEM_REPOSITORY_ROLE = new RepositoryRole(SYSTEM_ROLE, Collections.singleton("admin"), "system");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(create("/"));
@Rule
public ShiroRule shiroRule = new ShiroRule();
@Mock
private RepositoryRoleManager repositoryRoleManager;
@InjectMocks
private RepositoryRoleToRepositoryRoleDtoMapperImpl roleToDtoMapper;
@InjectMocks
private RepositoryRoleDtoToRepositoryRoleMapperImpl dtoToRoleMapper;
private RepositoryRoleCollectionToDtoMapper collectionToDtoMapper;
private Dispatcher dispatcher;
@Captor
private ArgumentCaptor<RepositoryRole> modifyCaptor;
@Captor
private ArgumentCaptor<RepositoryRole> createCaptor;
@Captor
private ArgumentCaptor<RepositoryRole> deleteCaptor;
@Before
public void init() {
collectionToDtoMapper = new RepositoryRoleCollectionToDtoMapper(roleToDtoMapper, resourceLinks);
RepositoryRoleCollectionResource collectionResource = new RepositoryRoleCollectionResource(repositoryRoleManager, dtoToRoleMapper, collectionToDtoMapper, resourceLinks);
RepositoryRoleResource roleResource = new RepositoryRoleResource(dtoToRoleMapper, roleToDtoMapper, repositoryRoleManager);
RepositoryRoleRootResource rootResource = new RepositoryRoleRootResource(Providers.of(collectionResource), Providers.of(roleResource));
doNothing().when(repositoryRoleManager).modify(modifyCaptor.capture());
when(repositoryRoleManager.create(createCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
doNothing().when(repositoryRoleManager).delete(deleteCaptor.capture());
dispatcher = createDispatcher(rootResource);
dispatcher.getProviderFactory().registerProviderInstance(new JSONContextResolver(new ObjectMapperProvider().get()));
when(repositoryRoleManager.get(CUSTOM_ROLE)).thenReturn(CUSTOM_REPOSITORY_ROLE);
when(repositoryRoleManager.get(SYSTEM_ROLE)).thenReturn(SYSTEM_REPOSITORY_ROLE);
when(repositoryRoleManager.getPage(any(), any(), anyInt(), anyInt())).thenReturn(new PageResult<>(asList(CUSTOM_REPOSITORY_ROLE, SYSTEM_REPOSITORY_ROLE), 2));
}
@Test
public void shouldGetNotFoundForNotExistingRole() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2 + "noSuchRole");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NOT_FOUND);
}
@Test
public void shouldGetCustomRole() throws URISyntaxException, UnsupportedEncodingException {
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2 + CUSTOM_ROLE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getContentAsString())
.contains(
"\"name\":\"" + CUSTOM_ROLE + "\"",
"\"verbs\":[\"verb\"]",
"\"self\":{\"href\":\"/v2/repositoryRoles/" + CUSTOM_ROLE + "\"}",
"\"update\":{\"href\":\"/v2/repositoryRoles/" + CUSTOM_ROLE + "\"}",
"\"delete\":{\"href\":\"/v2/repositoryRoles/" + CUSTOM_ROLE + "\"}"
);
}
@Test
public void shouldGetSystemRole() throws URISyntaxException, UnsupportedEncodingException {
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2 + SYSTEM_ROLE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getContentAsString())
.contains(
"\"name\":\"" + SYSTEM_ROLE + "\"",
"\"verbs\":[\"admin\"]",
"\"self\":{\"href\":\"/v2/repositoryRoles/" + SYSTEM_ROLE + "\"}"
)
.doesNotContain(
"\"delete\":{\"href\":\"/v2/repositoryRoles/" + CUSTOM_ROLE + "\"}",
"\"update\":{\"href\":\"/v2/repositoryRoles/" + CUSTOM_ROLE + "\"}"
);
}
@Test
@SubjectAware(username = "dent")
public void shouldNotGetDeleteLinkWithoutPermission() throws URISyntaxException, UnsupportedEncodingException {
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2 + CUSTOM_ROLE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getContentAsString())
.doesNotContain("delete");
}
@Test
public void shouldUpdateRole() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest
.put("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2 + CUSTOM_ROLE)
.contentType(VndMediaType.REPOSITORY_ROLE)
.content(content("{'name': '" + CUSTOM_ROLE + "', 'verbs': ['write', 'push']}"));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NO_CONTENT);
verify(repositoryRoleManager).modify(any());
assertThat(modifyCaptor.getValue().getName()).isEqualTo(CUSTOM_ROLE);
assertThat(modifyCaptor.getValue().getVerbs()).containsExactly("write", "push");
}
@Test
public void shouldNotChangeRoleName() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest
.put("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2 + CUSTOM_ROLE)
.contentType(VndMediaType.REPOSITORY_ROLE)
.content(content("{'name': 'changedName', 'verbs': ['write', 'push']}"));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
verify(repositoryRoleManager, never()).modify(any());
}
@Test
public void shouldFailForUpdateOfNotExistingRole() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest
.put("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2 + "noSuchRole")
.contentType(VndMediaType.REPOSITORY_ROLE)
.content(content("{'name': 'noSuchRole', 'verbs': ['write', 'push']}"));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NOT_FOUND);
verify(repositoryRoleManager, never()).modify(any());
}
@Test
public void shouldCreateRole() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2)
.contentType(VndMediaType.REPOSITORY_ROLE)
.content(content("{'name': 'newRole', 'verbs': ['write', 'push']}"));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_CREATED);
verify(repositoryRoleManager).create(any());
assertThat(createCaptor.getValue().getName()).isEqualTo("newRole");
assertThat(createCaptor.getValue().getVerbs()).containsExactly("write", "push");
Object location = response.getOutputHeaders().getFirst("Location");
assertThat(location).isEqualTo(create("/v2/repositoryRoles/newRole"));
}
@Test
public void shouldDeleteRole() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest
.delete("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2 + CUSTOM_ROLE);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NO_CONTENT);
verify(repositoryRoleManager).delete(any());
assertThat(deleteCaptor.getValue().getName()).isEqualTo(CUSTOM_ROLE);
}
@Test
public void shouldGetAllRoles() throws URISyntaxException, UnsupportedEncodingException {
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getContentAsString())
.contains(
"\"name\":\"" + CUSTOM_ROLE + "\"",
"\"name\":\"" + SYSTEM_ROLE + "\"",
"\"verbs\":[\"verb\"]",
"\"verbs\":[\"admin\"]",
"\"self\":{\"href\":\"/v2/repositoryRoles",
"\"delete\":{\"href\":\"/v2/repositoryRoles/" + CUSTOM_ROLE + "\"}",
"\"create\":{\"href\":\"/v2/repositoryRoles/\"}"
)
.doesNotContain(
"\"delete\":{\"href\":\"/v2/repositoryRoles/" + SYSTEM_ROLE + "\"}"
);
}
@Test
public void shouldFailForEmptyName() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2)
.contentType(VndMediaType.REPOSITORY_ROLE)
.content(content("{'name': '', 'verbs': ['write', 'push']}"));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
verify(repositoryRoleManager, never()).create(any());
}
@Test
public void shouldFailForMissingVerbs() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2)
.contentType(VndMediaType.REPOSITORY_ROLE)
.content(content("{'name': 'ok', 'verbs': []}"));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
verify(repositoryRoleManager, never()).create(any());
}
@Test
public void shouldFailForEmptyVerb() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2)
.contentType(VndMediaType.REPOSITORY_ROLE)
.content(content("{'name': 'ok', 'verbs': ['', 'push']}"));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
verify(repositoryRoleManager, never()).create(any());
}
@Test
@SubjectAware(username = "dent")
public void shouldNotGetCreateLinkWithoutPermission() throws URISyntaxException, UnsupportedEncodingException {
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRoleRootResource.REPOSITORY_ROLES_PATH_V2);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(response.getContentAsString())
.doesNotContain(
"create"
);
}
private byte[] content(String data) {
return data.replaceAll("'", "\"").getBytes();
}
}

View File

@@ -332,7 +332,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
.hasSize(1)
.allSatisfy(p -> {
assertThat(p.getName()).isEqualTo("trillian");
assertThat(p.getVerbs()).containsExactly("*");
assertThat(p.getRole()).isEqualTo("OWNER");
});
}

View File

@@ -42,8 +42,9 @@ public class ResourceLinksMock {
when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(uriInfo));
when(resourceLinks.merge()).thenReturn(new ResourceLinks.MergeLinks(uriInfo));
when(resourceLinks.permissions()).thenReturn(new ResourceLinks.PermissionsLinks(uriInfo));
when(resourceLinks.availableRepositoryPermissions()).thenReturn(new ResourceLinks.AvailableRepositoryPermissionLinks(uriInfo));
when(resourceLinks.repositoryTypeCollection()).thenReturn(new ResourceLinks.RepositoryTypeCollectionLinks(uriInfo));
when(resourceLinks.repositoryVerbs()).thenReturn(new ResourceLinks.RepositoryVerbLinks(uriInfo));
when(resourceLinks.repositoryRole()).thenReturn(new ResourceLinks.RepositoryRoleLinks(uriInfo));
when(resourceLinks.repositoryRoleCollection()).thenReturn(new ResourceLinks.RepositoryRoleCollectionLinks(uriInfo));
when(resourceLinks.namespaceStrategies()).thenReturn(new ResourceLinks.NamespaceStrategiesLinks(uriInfo));
return resourceLinks;

View File

@@ -0,0 +1,218 @@
package sonia.scm.repository;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import sonia.scm.NotFoundException;
import sonia.scm.ScmConstraintViolationException;
import sonia.scm.security.RepositoryPermissionProvider;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class DefaultRepositoryRoleManagerTest {
private static final String CUSTOM_ROLE_NAME = "customRole";
private static final String SYSTEM_ROLE_NAME = "systemRole";
private static final RepositoryRole CUSTOM_ROLE = new RepositoryRole(CUSTOM_ROLE_NAME, singletonList("custom"), "xml");
private static final RepositoryRole SYSTEM_ROLE = new RepositoryRole(SYSTEM_ROLE_NAME, singletonList("system"), "system");
private final Subject subject = mock(Subject.class);
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
@Mock
private RepositoryRoleDAO dao;
@Mock
private RepositoryPermissionProvider permissionProvider;
@InjectMocks
private DefaultRepositoryRoleManager manager;
@BeforeEach
void initUser() {
subjectThreadState.bind();
doAnswer(invocation -> {
String permission = invocation.getArguments()[0].toString();
if (!subject.isPermitted(permission)) {
throw new UnauthorizedException(permission);
}
return null;
}).when(subject).checkPermission(anyString());
ThreadContext.bind(subject);
}
@BeforeEach
void initDao() {
when(dao.getType()).thenReturn("xml");
}
@BeforeEach
void mockExistingRole() {
when(dao.get(CUSTOM_ROLE_NAME)).thenReturn(CUSTOM_ROLE);
when(permissionProvider.availableRoles()).thenReturn(asList(CUSTOM_ROLE, SYSTEM_ROLE));
}
@AfterEach
void cleanupContext() {
ThreadContext.unbindSubject();
}
@Nested
class WithAuthorizedUser {
@BeforeEach
void authorizeUser() {
when(subject.isPermitted("repositoryRole:read")).thenReturn(true);
when(subject.isPermitted("repositoryRole:modify")).thenReturn(true);
}
@Test
void shouldReturnNull_forNotExistingRole() {
RepositoryRole role = manager.get("noSuchRole");
assertThat(role).isNull();
}
@Test
void shouldReturnRole_forExistingRole() {
RepositoryRole role = manager.get(CUSTOM_ROLE_NAME);
assertThat(role).isNotNull();
}
@Test
void shouldCreateRole() {
RepositoryRole role = manager.create(new RepositoryRole("new", singletonList("custom"), null));
assertThat(role.getType()).isEqualTo("xml");
verify(dao).add(role);
}
@Test
void shouldNotCreateRole_whenSystemRoleExists() {
assertThrows(UnauthorizedException.class, () -> manager.create(new RepositoryRole(SYSTEM_ROLE_NAME, singletonList("custom"), null)));
verify(dao, never()).add(any());
}
@Test
void shouldModifyRole() {
RepositoryRole role = new RepositoryRole(CUSTOM_ROLE_NAME, singletonList("changed"), "xml");
manager.modify(role);
verify(dao).modify(role);
}
@Test
void shouldNotModifyRole_whenTypeChanged() {
assertThrows(ScmConstraintViolationException.class, () -> manager.modify(new RepositoryRole(CUSTOM_ROLE_NAME, singletonList("changed"), null)));
verify(dao, never()).modify(any());
}
@Test
void shouldNotModifyRole_whenRoleDoesNotExists() {
assertThrows(NotFoundException.class, () -> manager.modify(new RepositoryRole("noSuchRole", singletonList("changed"), null)));
verify(dao, never()).modify(any());
}
@Test
void shouldNotModifyRole_whenSystemRoleExists() {
assertThrows(UnauthorizedException.class, () -> manager.modify(new RepositoryRole(SYSTEM_ROLE_NAME, singletonList("changed"), null)));
verify(dao, never()).modify(any());
}
@Test
void shouldReturnAllRoles() {
List<RepositoryRole> allRoles = manager.getAll();
assertThat(allRoles).containsExactly(CUSTOM_ROLE, SYSTEM_ROLE);
}
@Test
void shouldReturnFilteredRoles() {
Collection<RepositoryRole> allRoles = manager.getAll(role -> CUSTOM_ROLE_NAME.equals(role.getName()), null);
assertThat(allRoles).containsExactly(CUSTOM_ROLE);
}
@Test
void shouldReturnOrderedFilteredRoles() {
Collection<RepositoryRole> allRoles =
manager.getAll(
role -> true,
Comparator.comparing(RepositoryRole::getType));
assertThat(allRoles).containsExactly(SYSTEM_ROLE, CUSTOM_ROLE);
}
@Test
void shouldReturnPaginatedRoles() {
Collection<RepositoryRole> allRoles =
manager.getAll(
Comparator.comparing(RepositoryRole::getType),
1, 1
);
assertThat(allRoles).containsExactly(CUSTOM_ROLE);
}
}
@Nested
class WithUnauthorizedUser {
@BeforeEach
void authorizeUser() {
when(subject.isPermitted(any(String.class))).thenReturn(false);
}
@Test
void shouldThrowException_forGet() {
assertThrows(UnauthorizedException.class, () -> manager.get("any"));
}
@Test
void shouldThrowException_forCreate() {
assertThrows(UnauthorizedException.class, () -> manager.create(new RepositoryRole("new", singletonList("custom"), null)));
verify(dao, never()).add(any());
}
@Test
void shouldThrowException_forModify() {
assertThrows(UnauthorizedException.class, () -> manager.modify(new RepositoryRole(CUSTOM_ROLE_NAME, singletonList("custom"), null)));
verify(dao, never()).modify(any());
}
@Test
void shouldReturnEmptyList() {
assertThat(manager.getAll()).isEmpty();
}
@Test
void shouldReturnEmptyFilteredList() {
assertThat(manager.getAll(x -> true, null)).isEmpty();
}
@Test
void shouldReturnEmptyPaginatedList() {
assertThat(manager.getAll(1, 1)).isEmpty();
}
}
}

View File

@@ -33,10 +33,10 @@ package sonia.scm.security;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.hamcrest.Matchers;
@@ -49,16 +49,19 @@ import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.group.GroupNames;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryPermission;
import sonia.scm.repository.RepositoryRole;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.user.User;
import sonia.scm.user.UserTestData;
import java.util.Collections;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.nullValue;
@@ -90,6 +93,9 @@ public class DefaultAuthorizationCollectorTest {
@Mock
private SecuritySystem securitySystem;
@Mock
private RepositoryPermissionProvider repositoryPermissionProvider;
private DefaultAuthorizationCollector collector;
@Rule
@@ -101,11 +107,11 @@ public class DefaultAuthorizationCollectorTest {
@Before
public void setUp(){
when(cacheManager.getCache(Mockito.any(String.class))).thenReturn(cache);
collector = new DefaultAuthorizationCollector(cacheManager, repositoryDAO, securitySystem);
collector = new DefaultAuthorizationCollector(cacheManager, repositoryDAO, securitySystem, repositoryPermissionProvider);
}
/**
* Tests {@link AuthorizationCollector#collect()} without user role.
* Tests {@link AuthorizationCollector#collect(PrincipalCollection)} ()} without user role.
*/
@Test
@SubjectAware
@@ -118,7 +124,7 @@ public class DefaultAuthorizationCollectorTest {
}
/**
* Tests {@link AuthorizationCollector#collect()} from cache.
* Tests {@link AuthorizationCollector#collect(PrincipalCollection)} from cache.
*/
@Test
@SubjectAware(
@@ -134,7 +140,7 @@ public class DefaultAuthorizationCollectorTest {
}
/**
* Tests {@link AuthorizationCollector#collect()} with cache.
* Tests {@link AuthorizationCollector#collect(PrincipalCollection)} ()} with cache.
*/
@Test
@SubjectAware(
@@ -148,7 +154,7 @@ public class DefaultAuthorizationCollectorTest {
}
/**
* Tests {@link AuthorizationCollector#collect()} without permissions.
* Tests {@link AuthorizationCollector#collect(PrincipalCollection)} ()} without permissions.
*/
@Test
@SubjectAware(
@@ -165,7 +171,7 @@ public class DefaultAuthorizationCollectorTest {
}
/**
* Tests {@link AuthorizationCollector#collect()} with repository permissions.
* Tests {@link AuthorizationCollector#collect(PrincipalCollection)} ()} with repository permissions.
*/
@Test
@SubjectAware(
@@ -191,7 +197,76 @@ public class DefaultAuthorizationCollectorTest {
}
/**
* Tests {@link AuthorizationCollector#collect()} with global permissions.
* Tests {@link AuthorizationCollector#collect(PrincipalCollection)} with repository roles.
*/
@Test
@SubjectAware(
configuration = "classpath:sonia/scm/shiro-001.ini"
)
public void testCollectWithRepositoryRolePermissions() {
when(repositoryPermissionProvider.availableRoles()).thenReturn(
asList(
new RepositoryRole("user role", singletonList("user"), "xml"),
new RepositoryRole("group role", singletonList("group"), "xml"),
new RepositoryRole("system role", singletonList("system"), "system")
));
String group = "heart-of-gold-crew";
authenticate(UserTestData.createTrillian(), group);
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
heartOfGold.setId("one");
heartOfGold.setPermissions(Lists.newArrayList(
new RepositoryPermission("trillian", "user role", false),
new RepositoryPermission("trillian", "system role", false)
));
Repository puzzle42 = RepositoryTestData.create42Puzzle();
puzzle42.setId("two");
RepositoryPermission permission = new RepositoryPermission(group, "group role", true);
puzzle42.setPermissions(Lists.newArrayList(permission));
when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold, puzzle42));
// execute and assert
AuthorizationInfo authInfo = collector.collect();
assertThat(authInfo.getRoles(), Matchers.containsInAnyOrder(Role.USER));
assertThat(authInfo.getObjectPermissions(), nullValue());
assertThat(authInfo.getStringPermissions(), containsInAnyOrder(
"user:autocomplete",
"group:autocomplete",
"user:changePassword:trillian",
"repository:user:one",
"repository:system:one",
"repository:group:two",
"user:read:trillian"));
}
/**
* Tests {@link AuthorizationCollector#collect(PrincipalCollection)} with repository roles.
*/
@Test(expected = IllegalStateException.class)
@SubjectAware(
configuration = "classpath:sonia/scm/shiro-001.ini"
)
public void testCollectWithUnknownRepositoryRole() {
when(repositoryPermissionProvider.availableRoles()).thenReturn(
singletonList(
new RepositoryRole("something", singletonList("something"), "xml")
));
String group = "heart-of-gold-crew";
authenticate(UserTestData.createTrillian(), group);
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
heartOfGold.setId("one");
heartOfGold.setPermissions(singletonList(
new RepositoryPermission("trillian", "unknown", false)
));
when(repositoryDAO.getAll()).thenReturn(Lists.newArrayList(heartOfGold));
// execute and assert
AuthorizationInfo authInfo = collector.collect();
}
/**
* Tests {@link AuthorizationCollector#collect(PrincipalCollection)} ()} with global permissions.
*/
@Test
@SubjectAware(

View File

@@ -1,72 +1,51 @@
package sonia.scm.security;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.util.ClassLoaders;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.RepositoryRole;
import sonia.scm.repository.RepositoryRoleDAO;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RepositoryPermissionProviderTest {
private RepositoryPermissionProvider repositoryPermissionProvider;
private String[] allVerbsFromRepositoryClass;
@Mock
SystemRepositoryPermissionProvider systemRepositoryPermissionProvider;
@Mock
RepositoryRoleDAO repositoryRoleDAO;
@InjectMocks
RepositoryPermissionProvider repositoryPermissionProvider;
@BeforeEach
void init() {
PluginLoader pluginLoader = mock(PluginLoader.class);
when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class));
repositoryPermissionProvider = new RepositoryPermissionProvider(pluginLoader);
allVerbsFromRepositoryClass = Arrays.stream(RepositoryPermissions.class.getDeclaredFields())
.filter(field -> field.getName().startsWith("ACTION_"))
.filter(field -> !field.getName().equals("ACTION_HEALTHCHECK"))
.map(this::getString)
.filter(verb -> !"create".equals(verb))
.toArray(String[]::new);
@Test
void shouldReturnVerbsFromSystem() {
List<String> expectedVerbs = asList("verb1", "verb2");
when(systemRepositoryPermissionProvider.availableVerbs()).thenReturn(expectedVerbs);
Collection<String> actualVerbs = repositoryPermissionProvider.availableVerbs();
assertThat(actualVerbs).isEqualTo(expectedVerbs);
}
@Test
void shouldReadAvailableRoles() {
assertThat(repositoryPermissionProvider.availableRoles()).isNotEmpty();
assertThat(repositoryPermissionProvider.availableRoles()).allSatisfy(this::containsOnlyAvailableVerbs);
}
void shouldReturnJoinedRolesFromSystemAndDao() {
RepositoryRole systemRole = new RepositoryRole("roleSystem", singletonList("verb1"), "system");
RepositoryRole daoRole = new RepositoryRole("roleDao", singletonList("verb1"), "xml");
when(systemRepositoryPermissionProvider.availableRoles()).thenReturn(singletonList(systemRole));
when(repositoryRoleDAO.getAll()).thenReturn(singletonList(daoRole));
private void containsOnlyAvailableVerbs(RepositoryRole role) {
assertThat(role.getVerbs()).isSubsetOf(repositoryPermissionProvider.availableVerbs());
}
Collection<RepositoryRole> actualRoles = repositoryPermissionProvider.availableRoles();
@Test
void shouldReadAvailableVerbsFromRepository() {
assertThat(repositoryPermissionProvider.availableVerbs()).contains(allVerbsFromRepositoryClass);
}
@Test
void shouldMergeRepositoryRoles() {
Collection<String> verbsInMergedRole = repositoryPermissionProvider
.availableRoles()
.stream()
.filter(r -> "READ".equals(r.getName()))
.findFirst()
.get()
.getVerbs();
assertThat(verbsInMergedRole).contains("read", "pull", "test");
}
private String getString(Field field) {
try {
return (String) field.get(null);
} catch (IllegalAccessException e) {
fail(e);
return null;
}
assertThat(actualRoles).containsExactly(systemRole, daoRole);
}
}

View File

@@ -0,0 +1,73 @@
package sonia.scm.security;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.RepositoryRole;
import sonia.scm.util.ClassLoaders;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class SystemRepositoryPermissionProviderTest {
private SystemRepositoryPermissionProvider repositoryPermissionProvider;
private String[] allVerbsFromRepositoryClass;
@BeforeEach
void init() {
PluginLoader pluginLoader = mock(PluginLoader.class);
when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class));
repositoryPermissionProvider = new SystemRepositoryPermissionProvider(pluginLoader);
allVerbsFromRepositoryClass = Arrays.stream(RepositoryPermissions.class.getDeclaredFields())
.filter(field -> field.getName().startsWith("ACTION_"))
.filter(field -> !field.getName().equals("ACTION_HEALTHCHECK"))
.map(this::getString)
.filter(verb -> !"create".equals(verb))
.toArray(String[]::new);
}
@Test
void shouldReadAvailableRoles() {
assertThat(repositoryPermissionProvider.availableRoles()).isNotEmpty();
assertThat(repositoryPermissionProvider.availableRoles()).allSatisfy(this::containsOnlyAvailableVerbs);
}
private void containsOnlyAvailableVerbs(RepositoryRole role) {
assertThat(role.getVerbs()).isSubsetOf(repositoryPermissionProvider.availableVerbs());
}
@Test
void shouldReadAvailableVerbsFromRepository() {
assertThat(repositoryPermissionProvider.availableVerbs()).contains(allVerbsFromRepositoryClass);
}
@Test
void shouldMergeRepositoryRoles() {
Collection<String> verbsInMergedRole = repositoryPermissionProvider
.availableRoles()
.stream()
.filter(r -> "READ".equals(r.getName()))
.findFirst()
.get()
.getVerbs();
assertThat(verbsInMergedRole).contains("read", "pull", "test");
}
private String getString(Field field) {
try {
return (String) field.get(null);
} catch (IllegalAccessException e) {
fail(e);
return null;
}
}
}