mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 08:55:44 +01:00
Use links from protocols instead of resource links
Add links only for requests with according permissions
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user