diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bb0f05a73..8cacdf9a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Internal server error for git sub modules without tree object ([#1397](https://github.com/scm-manager/scm-manager/pull/1397)) - Do not expose subversion commit with id 0 ([#1395](https://github.com/scm-manager/scm-manager/pull/1395)) +- Disable cloning repositories via ssh for anonymous users ([#1403](https://github.com/scm-manager/scm-manager/pull/1403)) - SVN diff with property changes ([#1400](https://github.com/scm-manager/scm-manager/pull/1400)) - Branches link in repository overview ([#1404](https://github.com/scm-manager/scm-manager/pull/1404)) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java index aa2a41782d..a6b4265239 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java @@ -34,6 +34,7 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.RepositoryServiceProvider; import sonia.scm.repository.work.WorkdirProvider; +import sonia.scm.security.Authentications; import sonia.scm.user.EMail; import javax.annotation.Nullable; @@ -453,7 +454,8 @@ public final class RepositoryService implements Closeable { public Stream getSupportedProtocols() { return protocolProviders.stream() .filter(protocolProvider -> protocolProvider.getType().equals(getRepository().getType())) - .map(this::createProviderInstanceForRepository); + .map(this::createProviderInstanceForRepository) + .filter(protocol -> !Authentications.isAuthenticatedSubjectAnonymous() || protocol.isAnonymousEnabled()); } @SuppressWarnings({"rawtypes", "java:S3740"}) diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ScmProtocol.java b/scm-core/src/main/java/sonia/scm/repository/api/ScmProtocol.java index 135191d5eb..ffdab2fc11 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/ScmProtocol.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/ScmProtocol.java @@ -40,4 +40,11 @@ public interface ScmProtocol { * The URL to access the repository providing this protocol. */ String getUrl(); + + /** + * Whether the protocol can be used as an anonymous user. + */ + default boolean isAnonymousEnabled() { + return true; + } } diff --git a/scm-core/src/test/java/sonia/scm/repository/api/RepositoryServiceTest.java b/scm-core/src/test/java/sonia/scm/repository/api/RepositoryServiceTest.java index 8394e3c582..c702cc64de 100644 --- a/scm-core/src/test/java/sonia/scm/repository/api/RepositoryServiceTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/api/RepositoryServiceTest.java @@ -24,7 +24,15 @@ package sonia.scm.repository.api; -import org.junit.Test; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ThreadContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.SCMContext; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.Repository; import sonia.scm.repository.spi.HttpScmProtocol; @@ -42,16 +50,32 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.util.IterableUtil.sizeOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -public class RepositoryServiceTest { +@ExtendWith(MockitoExtension.class) +class RepositoryServiceTest { private final RepositoryServiceProvider provider = mock(RepositoryServiceProvider.class); private final Repository repository = new Repository("", "git", "space", "repo"); private final EMail eMail = new EMail(new ScmConfiguration()); + @Mock + private Subject subject; + + @BeforeEach + void bindSubject() { + ThreadContext.bind(subject); + } + + @AfterEach + void unbindSubject() { + ThreadContext.unbindSubject(); + } + @Test - public void shouldReturnMatchingProtocolsFromProvider() { + void shouldReturnMatchingProtocolsFromProvider() { + when(subject.getPrincipal()).thenReturn("Hitchhiker"); RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail); Stream supportedProtocols = repositoryService.getSupportedProtocols(); @@ -59,7 +83,17 @@ public class RepositoryServiceTest { } @Test - public void shouldFindKnownProtocol() { + void shouldFilterOutNonAnonymousEnabledProtocolsForAnonymousUser() { + when(subject.getPrincipal()).thenReturn(SCMContext.USER_ANONYMOUS); + RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Stream.of(new DummyScmProtocolProvider(), new DummyScmProtocolProvider(false)).collect(Collectors.toSet()), null, eMail); + Stream supportedProtocols = repositoryService.getSupportedProtocols(); + + assertThat(sizeOf(supportedProtocols.collect(Collectors.toList()))).isEqualTo(1); + } + + @Test + void shouldFindKnownProtocol() { + when(subject.getPrincipal()).thenReturn("Hitchhiker"); RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail); HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class); @@ -68,23 +102,44 @@ public class RepositoryServiceTest { } @Test - public void shouldFailForUnknownProtocol() { + void shouldFailForUnknownProtocol() { + when(subject.getPrincipal()).thenReturn("Hitchhiker"); RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail); assertThrows(IllegalArgumentException.class, () -> repositoryService.getProtocol(UnknownScmProtocol.class)); } private static class DummyHttpProtocol extends HttpScmProtocol { - public DummyHttpProtocol(Repository repository) { + + private final boolean anonymousEnabled; + + public DummyHttpProtocol(Repository repository, boolean anonymousEnabled) { super(repository, ""); + this.anonymousEnabled = anonymousEnabled; } @Override public void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) { } + + @Override + public boolean isAnonymousEnabled() { + return anonymousEnabled; + } } private static class DummyScmProtocolProvider implements ScmProtocolProvider { + + private final boolean anonymousEnabled; + + public DummyScmProtocolProvider() { + this(true); + } + + public DummyScmProtocolProvider(boolean anonymousEnabled) { + this.anonymousEnabled = anonymousEnabled; + } + @Override public String getType() { return "git"; @@ -92,9 +147,10 @@ public class RepositoryServiceTest { @Override public ScmProtocol get(Repository repository) { - return new DummyHttpProtocol(repository); + return new DummyHttpProtocol(repository, anonymousEnabled); } } - private interface UnknownScmProtocol extends ScmProtocol {} + private interface UnknownScmProtocol extends ScmProtocol { + } }