diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index e94fabfa60..3fcea05d23 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -185,7 +185,7 @@ public class ScmConfiguration implements Configuration { private boolean enabledXsrfProtection = true; @XmlElement(name = "default-namespace-strategy") - private String defaultNamespaceStrategy = "sonia.scm.repository.DefaultNamespaceStrategy"; + private String defaultNamespaceStrategy = "sonia.scm.repository.UsernameNamespaceStrategy"; /** diff --git a/scm-webapp/src/main/java/sonia/scm/repository/CurrentYearNamespaceStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/CurrentYearNamespaceStrategy.java new file mode 100644 index 0000000000..6c9be7c2ac --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/CurrentYearNamespaceStrategy.java @@ -0,0 +1,29 @@ +package sonia.scm.repository; + +import com.google.common.annotations.VisibleForTesting; +import sonia.scm.plugin.Extension; + +import javax.inject.Inject; +import java.time.Clock; +import java.time.Year; + +@Extension +public class CurrentYearNamespaceStrategy implements NamespaceStrategy { + + private final Clock clock; + + @Inject + public CurrentYearNamespaceStrategy() { + this(Clock.systemDefaultZone()); + } + + @VisibleForTesting + CurrentYearNamespaceStrategy(Clock clock) { + this.clock = clock; + } + + @Override + public String createNamespace(Repository repository) { + return String.valueOf(Year.now(clock).getValue()); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/CustomNamespaceStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/CustomNamespaceStrategy.java new file mode 100644 index 0000000000..052465f9b7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/CustomNamespaceStrategy.java @@ -0,0 +1,20 @@ +package sonia.scm.repository; + +import sonia.scm.plugin.Extension; +import sonia.scm.util.ValidationUtil; + +import static sonia.scm.ScmConstraintViolationException.Builder.doThrow; + +@Extension +public class CustomNamespaceStrategy implements NamespaceStrategy { + @Override + public String createNamespace(Repository repository) { + String namespace = repository.getNamespace(); + + doThrow() + .violation("invalid namespace", "namespace") + .when(!ValidationUtil.isRepositoryNameValid(namespace)); + + return namespace; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceStrategy.java deleted file mode 100644 index 35a5abea24..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceStrategy.java +++ /dev/null @@ -1,24 +0,0 @@ -package sonia.scm.repository; - -import com.google.common.base.Strings; -import org.apache.shiro.SecurityUtils; -import sonia.scm.plugin.Extension; - -/** - * The DefaultNamespaceStrategy returns the predefined namespace of the given repository, if the namespace was not set - * the username of the currently loggedin user is used. - * - * @since 2.0.0 - */ -@Extension -public class DefaultNamespaceStrategy implements NamespaceStrategy { - - @Override - public String createNamespace(Repository repository) { - String namespace = repository.getNamespace(); - if (Strings.isNullOrEmpty(namespace)) { - namespace = SecurityUtils.getSubject().getPrincipal().toString(); - } - return namespace; - } -} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/RepositoryTypeNamespaceStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryTypeNamespaceStrategy.java new file mode 100644 index 0000000000..8643316230 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryTypeNamespaceStrategy.java @@ -0,0 +1,11 @@ +package sonia.scm.repository; + +import sonia.scm.plugin.Extension; + +@Extension +public class RepositoryTypeNamespaceStrategy implements NamespaceStrategy { + @Override + public String createNamespace(Repository repository) { + return repository.getType(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/UsernameNamespaceStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/UsernameNamespaceStrategy.java new file mode 100644 index 0000000000..9ddd9eab5d --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/UsernameNamespaceStrategy.java @@ -0,0 +1,13 @@ +package sonia.scm.repository; + +import org.apache.shiro.SecurityUtils; +import sonia.scm.plugin.Extension; + +@Extension +public class UsernameNamespaceStrategy implements NamespaceStrategy { + + @Override + public String createNamespace(Repository repository) { + return SecurityUtils.getSubject().getPrincipal().toString(); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/CurrentYearNamespaceStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/repository/CurrentYearNamespaceStrategyTest.java new file mode 100644 index 0000000000..bc59940791 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/CurrentYearNamespaceStrategyTest.java @@ -0,0 +1,39 @@ +package sonia.scm.repository; + +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 java.time.Clock; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CurrentYearNamespaceStrategyTest { + + @Mock + private Clock clock; + private NamespaceStrategy namespaceStrategy; + + @BeforeEach + void setupObjectUnderTest() { + namespaceStrategy = new CurrentYearNamespaceStrategy(clock); + } + + @Test + void shouldReturn1985() { + LocalDateTime dateTime = LocalDateTime.of(1985, 4, 9, 21, 42); + when(clock.instant()).thenReturn(dateTime.toInstant(ZoneOffset.UTC)); + when(clock.getZone()).thenReturn(ZoneId.systemDefault()); + + String namespace = namespaceStrategy.createNamespace(RepositoryTestData.createHeartOfGold()); + assertThat(namespace).isEqualTo("1985"); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/CustomNamespaceStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/repository/CustomNamespaceStrategyTest.java new file mode 100644 index 0000000000..4c1cc1568c --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/CustomNamespaceStrategyTest.java @@ -0,0 +1,28 @@ +package sonia.scm.repository; + +import org.junit.jupiter.api.Test; +import sonia.scm.ScmConstraintViolationException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CustomNamespaceStrategyTest { + + private final NamespaceStrategy namespaceStrategy = new CustomNamespaceStrategy(); + + @Test + void shouldReturnNamespaceFromRepository() { + Repository heartOfGold = RepositoryTestData.createHeartOfGold(); + assertThat(namespaceStrategy.createNamespace(heartOfGold)).isEqualTo(RepositoryTestData.NAMESPACE); + } + + @Test + void shouldThrowAnValidationExceptionForAnInvalidNamespace() { + Repository repository = new Repository(); + repository.setNamespace(".."); + repository.setName("."); + + assertThrows(ScmConstraintViolationException.class, () -> namespaceStrategy.createNamespace(repository)); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultNamespaceStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultNamespaceStrategyTest.java deleted file mode 100644 index 257ae5cff1..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultNamespaceStrategyTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package sonia.scm.repository; - -import com.github.sdorra.shiro.ShiroRule; -import com.github.sdorra.shiro.SubjectAware; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.*; - -@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini") -public class DefaultNamespaceStrategyTest { - - @Rule - public ShiroRule shiroRule = new ShiroRule(); - - private DefaultNamespaceStrategy namespaceStrategy = new DefaultNamespaceStrategy(); - - @Test - @SubjectAware(username = "trillian", password = "secret") - public void testNamespaceStrategyWithoutPreset() { - assertEquals("trillian", namespaceStrategy.createNamespace(new Repository())); - } - - @Test - @SubjectAware(username = "trillian", password = "secret") - public void testNamespaceStrategyWithPreset() { - Repository repository = new Repository(); - repository.setNamespace("awesome"); - assertEquals("awesome", namespaceStrategy.createNamespace(repository)); - } - -} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/RepositoryTypeNamespaceStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryTypeNamespaceStrategyTest.java new file mode 100644 index 0000000000..b157a4369f --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryTypeNamespaceStrategyTest.java @@ -0,0 +1,20 @@ +package sonia.scm.repository; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class RepositoryTypeNamespaceStrategyTest { + + private final RepositoryTypeNamespaceStrategy namespaceStrategy = new RepositoryTypeNamespaceStrategy(); + + @Test + void shouldReturnTypeOfRepository() { + Repository git = RepositoryTestData.create42Puzzle("git"); + assertThat(namespaceStrategy.createNamespace(git)).isEqualTo("git"); + + Repository hg = RepositoryTestData.create42Puzzle("hg"); + assertThat(namespaceStrategy.createNamespace(hg)).isEqualTo("hg"); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/UsernameNamespaceStrategyTest.java b/scm-webapp/src/test/java/sonia/scm/repository/UsernameNamespaceStrategyTest.java new file mode 100644 index 0000000000..1f3b49aa68 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/UsernameNamespaceStrategyTest.java @@ -0,0 +1,42 @@ +package sonia.scm.repository; + + +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 static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UsernameNamespaceStrategyTest { + + @Mock + private Subject subject; + + private final NamespaceStrategy usernameNamespaceStrategy = new UsernameNamespaceStrategy(); + + @BeforeEach + void setupSubject() { + ThreadContext.bind(subject); + } + + @AfterEach + void clearThreadContext() { + ThreadContext.unbindSubject(); + } + + @Test + void shouldReturnPrimaryPrincipal() { + when(subject.getPrincipal()).thenReturn("trillian"); + + String namespace = usernameNamespaceStrategy.createNamespace(RepositoryTestData.createHeartOfGold()); + assertThat(namespace).isEqualTo("trillian"); + } + +}