Use links from protocols instead of resource links

Add links only for requests with according permissions
This commit is contained in:
René Pfeuffer
2018-09-04 12:40:41 +02:00
parent b55955f273
commit 3f772b3688
9 changed files with 132 additions and 20 deletions

View File

@@ -42,7 +42,7 @@ import sonia.scm.repository.spi.RepositoryServiceProvider;
import java.io.Closeable;
import java.io.IOException;
import java.util.Set;
import java.util.Collection;
/**
* From the {@link RepositoryService} it is possible to access all commands for
@@ -359,7 +359,7 @@ public final class RepositoryService implements Closeable {
}
public Set<ScmProtocol> getSupportedProtocols() {
public Collection<ScmProtocol> getSupportedProtocols() {
return provider.getSupportedProtocols();
}

View File

@@ -1,5 +1,7 @@
package sonia.scm.repository.api;
import javax.ws.rs.core.UriInfo;
/**
* An ScmProtocol represents a concrete protocol provided by the SCM-Manager instance
* to interact with a repository depending on its type. There may be multiple protocols
@@ -15,5 +17,5 @@ public interface ScmProtocol {
/**
* The URL to access the repository providing this protocol.
*/
String getUrl();
String getUrl(UriInfo uriInfo);
}

View File

@@ -7,8 +7,9 @@ import sonia.scm.repository.spi.RepositoryServiceProvider;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.UriInfo;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.util.IterableUtil.sizeOf;
@@ -26,7 +27,7 @@ public class RepositoryServiceTest {
when(provider.getSupportedProtocols()).thenReturn(Collections.singleton(new DummyHttpProtocol()));
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null);
Set<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
Collection<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
assertThat(sizeOf(supportedProtocols)).isEqualTo(1);
}
@@ -38,7 +39,7 @@ public class RepositoryServiceTest {
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null);
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
assertThat(protocol.getUrl()).isEqualTo("dummy");
assertThat(protocol.getUrl(null)).isEqualTo("dummy");
}
@Test
@@ -54,7 +55,7 @@ public class RepositoryServiceTest {
private static class DummyHttpProtocol implements HttpScmProtocol {
@Override
public String getUrl() {
public String getUrl(UriInfo uriInfo) {
return "dummy";
}

View File

@@ -1,6 +1,7 @@
package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
@@ -11,9 +12,14 @@ import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.ScmProtocol;
import java.util.Collection;
import java.util.List;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
import static java.util.stream.Collectors.toList;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@@ -24,13 +30,14 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
private ResourceLinks resourceLinks;
@Inject
private RepositoryServiceFactory serviceFactory;
@Inject
private UriInfoStore uriInfoStore;
abstract HealthCheckFailureDto toDto(HealthCheckFailure failure);
@AfterMapping
void appendLinks(Repository repository, @MappingTarget RepositoryDto target) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.repository().self(target.getNamespace(), target.getName()));
linksBuilder.single(link("httpProtocol", resourceLinks.repository().clone(target.getType(), target.getNamespace(), target.getName())));
if (RepositoryPermissions.delete(repository).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.repository().delete(target.getNamespace(), target.getName())));
}
@@ -39,6 +46,14 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
linksBuilder.single(link("permissions", resourceLinks.permission().all(target.getNamespace(), target.getName())));
}
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
if (RepositoryPermissions.pull(repository).isPermitted()) {
Collection<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
List<Link> protocolLinks = supportedProtocols
.stream()
.map(this::createProtocolLink)
.collect(toList());
linksBuilder.array(protocolLinks);
}
if (repositoryService.isSupported(Command.TAGS)) {
linksBuilder.single(link("tags", resourceLinks.tag().all(target.getNamespace(), target.getName())));
}
@@ -50,4 +65,8 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(target.getNamespace(), target.getName())));
target.add(linksBuilder.build());
}
private Link createProtocolLink(ScmProtocol protocol) {
return Link.linkBuilder("protocol", protocol.getUrl(uriInfoStore.get())).withName(protocol.getType()).build();
}
}

View File

@@ -4,7 +4,6 @@ import sonia.scm.repository.NamespaceAndName;
import javax.inject.Inject;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
class ResourceLinks {
@@ -141,10 +140,6 @@ class ResourceLinks {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("get").parameters().href();
}
String clone(String type, String namespace, String name) {
return uriInfo.getBaseUri().resolve(URI.create("../../" + type + "/" + namespace + "/" + name)).toASCIIString();
}
String delete(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("delete").parameters().href();
}

View File

@@ -0,0 +1,25 @@
package sonia.scm.api.v2.resources;
import sonia.scm.repository.api.ScmProtocol;
import javax.ws.rs.core.UriInfo;
class MockScmProtocol implements ScmProtocol {
private final String type;
private final String protocol;
public MockScmProtocol(String type, String protocol) {
this.type = type;
this.protocol = protocol;
}
@Override
public String getType() {
return type;
}
@Override
public String getUrl(UriInfo uriInfo) {
return protocol;
}
}

View File

@@ -9,7 +9,6 @@ import org.jboss.resteasy.mock.MockHttpResponse;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
@@ -20,6 +19,7 @@ import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryIsNotArchivedException;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.web.VndMediaType;
@@ -29,6 +29,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
@@ -64,8 +65,12 @@ public class RepositoryRootResourceTest {
@Mock
private RepositoryManager repositoryManager;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@Mock
private RepositoryServiceFactory serviceFactory;
@Mock
private RepositoryService service;
@Mock
private UriInfoStore uriInfoStore;
private final URI baseUri = URI.create("/");
@@ -83,6 +88,7 @@ public class RepositoryRootResourceTest {
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
RepositoryCollectionResource repositoryCollectionResource = new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks);
RepositoryRootResource repositoryRootResource = new RepositoryRootResource(MockProvider.of(repositoryResource), MockProvider.of(repositoryCollectionResource));
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
dispatcher = createDispatcher(repositoryRootResource);
}
@@ -267,6 +273,20 @@ public class RepositoryRootResourceTest {
assertFalse(modifiedRepositoryCaptor.getValue().getPermissions().isEmpty());
}
@Test
public void shouldCreateArrayOfProtocolUrls() throws Exception {
mockRepository("space", "repo");
when(service.getSupportedProtocols()).thenReturn(asList(new MockScmProtocol("http", "http://"), new MockScmProtocol("ssh", "ssh://")));
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(SC_OK, response.getStatus());
assertTrue(response.getContentAsString().contains("\"protocol\":[{\"href\":\"http://\",\"name\":\"http\"},{\"href\":\"ssh://\",\"name\":\"ssh\"}]"));
}
private PageResult<Repository> createSingletonPageResult(Repository repository) {
return new PageResult<>(singletonList(repository), 0);
}

View File

@@ -7,7 +7,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import sonia.scm.repository.HealthCheckFailure;
@@ -15,13 +14,18 @@ import sonia.scm.repository.Permission;
import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.ScmProtocol;
import java.net.URI;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -39,8 +43,12 @@ public class RepositoryToRepositoryDtoMapperTest {
private final URI baseUri = URI.create("http://example.com/base/");
@SuppressWarnings("unused") // Is injected
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@Mock//(answer = Answers.RETURNS_DEEP_STUBS)
private RepositoryServiceFactory serviceFactory;
@Mock
private RepositoryService repositoryService;
@Mock
private UriInfoStore uriInfoStore;
@InjectMocks
private RepositoryToRepositoryDtoMapperImpl mapper;
@@ -48,7 +56,9 @@ public class RepositoryToRepositoryDtoMapperTest {
@Before
public void init() {
initMocks(this);
when(serviceFactory.create(any(Repository.class)).isSupported(any(Command.class))).thenReturn(true);
when(serviceFactory.create(any(Repository.class))).thenReturn(repositoryService);
when(repositoryService.isSupported(any(Command.class))).thenReturn(true);
when(repositoryService.getSupportedProtocols()).thenReturn(emptySet());
}
@After
@@ -129,14 +139,14 @@ public class RepositoryToRepositoryDtoMapperTest {
@Test
public void shouldNotCreateTagsLink_ifNotSupported() {
when(serviceFactory.create(any(Repository.class)).isSupported(Command.TAGS)).thenReturn(false);
when(repositoryService.isSupported(Command.TAGS)).thenReturn(false);
RepositoryDto dto = mapper.map(createTestRepository());
assertFalse(dto.getLinks().getLinkBy("tags").isPresent());
}
@Test
public void shouldNotCreateBranchesLink_ifNotSupported() {
when(serviceFactory.create(any(Repository.class)).isSupported(Command.BRANCHES)).thenReturn(false);
when(repositoryService.isSupported(Command.BRANCHES)).thenReturn(false);
RepositoryDto dto = mapper.map(createTestRepository());
assertFalse(dto.getLinks().getLinkBy("branches").isPresent());
}
@@ -165,6 +175,43 @@ public class RepositoryToRepositoryDtoMapperTest {
dto.getLinks().getLinkBy("permissions").get().getHref());
}
@Test
public void shouldCreateCorrectProtocolLinks() {
when(repositoryService.getSupportedProtocols()).thenReturn(
asList(mockProtocol("http", "http://scm"), mockProtocol("other", "some://protocol"))
);
RepositoryDto dto = mapper.map(createTestRepository());
assertTrue("should contain http link", dto.getLinks().stream().anyMatch(l -> l.getName().equals("http") && l.getHref().equals("http://scm")));
assertTrue("should contain other link", dto.getLinks().stream().anyMatch(l -> l.getName().equals("other") && l.getHref().equals("some://protocol")));
}
@Test
@SubjectAware(username = "community")
public void shouldCreateProtocolLinksForPullPermission() {
when(repositoryService.getSupportedProtocols()).thenReturn(
asList(mockProtocol("http", "http://scm"), mockProtocol("other", "some://protocol"))
);
RepositoryDto dto = mapper.map(createTestRepository());
assertEquals(2, dto.getLinks().getLinksBy("protocol").size());
}
@Test
@SubjectAware(username = "unpriv")
public void shouldNotCreateProtocolLinksWithoutPullPermission() {
when(repositoryService.getSupportedProtocols()).thenReturn(
asList(mockProtocol("http", "http://scm"), mockProtocol("other", "some://protocol"))
);
RepositoryDto dto = mapper.map(createTestRepository());
assertTrue(dto.getLinks().getLinksBy("protocol").isEmpty());
}
private ScmProtocol mockProtocol(String type, String protocol) {
return new MockScmProtocol(type, protocol);
}
private Repository createTestRepository() {
Repository repository = new Repository();
repository.setNamespace("testspace");
@@ -179,4 +226,5 @@ public class RepositoryToRepositoryDtoMapperTest {
return repository;
}
}

View File

@@ -3,9 +3,11 @@ trillian = secret, admin
dent = secret, creator, heartOfGold, puzzle42
unpriv = secret
crato = secret, creator
community = secret, oss
[roles]
admin = *
creator = repository:create
heartOfGold = "repository:read,modify,delete:hof"
puzzle42 = "repository:read,write:p42"
oss = "repository:pull"