diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java index e5e754afea..73c144564b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java @@ -6,6 +6,8 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint; import sonia.scm.config.ConfigurationPermissions; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.NamespaceStrategyValidator; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; import sonia.scm.util.ScmConfigurationUtil; import sonia.scm.web.VndMediaType; @@ -29,15 +31,17 @@ public class ConfigResource { private final ScmConfigurationToConfigDtoMapper configToDtoMapper; private final ScmConfiguration configuration; private final NamespaceStrategyValidator namespaceStrategyValidator; + private final UserManager userManager; @Inject public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToConfigDtoMapper configToDtoMapper, - ScmConfiguration configuration, NamespaceStrategyValidator namespaceStrategyValidator) { + ScmConfiguration configuration, NamespaceStrategyValidator namespaceStrategyValidator, UserManager userManager) { this.dtoToConfigMapper = dtoToConfigMapper; this.configToDtoMapper = configToDtoMapper; this.configuration = configuration; this.namespaceStrategyValidator = namespaceStrategyValidator; + this.userManager = userManager; } /** @@ -92,6 +96,10 @@ public class ConfigResource { ScmConfigurationUtil.getInstance().store(configuration); } + if (config.isAnonymousAccessEnabled() && !userManager.contains("_anonymous")) { + userManager.create(new User("_anonymous")); + } + return Response.noContent().build(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java index bbc0dce120..69cb8a9224 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/SetupContextListener.java @@ -4,6 +4,7 @@ import com.google.common.annotations.VisibleForTesting; import org.apache.shiro.authc.credential.PasswordService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.config.ScmConfiguration; import sonia.scm.plugin.Extension; import sonia.scm.security.PermissionAssigner; import sonia.scm.security.PermissionDescriptor; @@ -47,12 +48,14 @@ public class SetupContextListener implements ServletContextListener { private final UserManager userManager; private final PasswordService passwordService; private final PermissionAssigner permissionAssigner; + private final ScmConfiguration scmConfiguration; @Inject - public SetupAction(UserManager userManager, PasswordService passwordService, PermissionAssigner permissionAssigner) { + public SetupAction(UserManager userManager, PasswordService passwordService, PermissionAssigner permissionAssigner, ScmConfiguration scmConfiguration) { this.userManager = userManager; this.passwordService = passwordService; this.permissionAssigner = permissionAssigner; + this.scmConfiguration = scmConfiguration; } @Override @@ -60,6 +63,13 @@ public class SetupContextListener implements ServletContextListener { if (isFirstStart()) { createAdminAccount(); } + if (anonymousUserRequiredButNotExists()) { + userManager.create(new User("_anonymous")); + } + } + + private boolean anonymousUserRequiredButNotExists() { + return scmConfiguration.isAnonymousAccessEnabled() && !userManager.contains("_anonymous"); } private boolean isFirstStart() { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java index 15c23dbd3c..eb86e0aa33 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java @@ -16,6 +16,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.NamespaceStrategyValidator; +import sonia.scm.user.User; +import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; import javax.servlet.http.HttpServletResponse; @@ -29,7 +31,9 @@ import java.nio.charset.StandardCharsets; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @SubjectAware( @@ -50,6 +54,9 @@ public class ConfigResourceTest { @SuppressWarnings("unused") // Is injected private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); + @Mock + private UserManager userManager; + @Mock private NamespaceStrategyValidator namespaceStrategyValidator; @@ -69,7 +76,7 @@ public class ConfigResourceTest { public void prepareEnvironment() { initMocks(this); - ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration(), namespaceStrategyValidator); + ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration(), namespaceStrategyValidator, userManager); dispatcher.getRegistry().addSingletonResource(configResource); } @@ -114,6 +121,45 @@ public class ConfigResourceTest { assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config")); } + @Test + @SubjectAware(username = "readWrite") + public void shouldUpdateConfigAndCreateAnonymousUser() throws URISyntaxException, IOException { + MockHttpRequest request = post("sonia/scm/api/v2/config-test-update-with-anonymous-access.json"); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + + request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); + response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"newPassword\"")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config")); + assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config")); + verify(userManager).create(new User("_anonymous")); + } + + @Test + @SubjectAware(username = "readWrite") + public void shouldUpdateConfigAndNotCreateAnonymousUserIfAlreadyExists() throws URISyntaxException, IOException { + when(userManager.contains("_anonymous")).thenReturn(true); + MockHttpRequest request = post("sonia/scm/api/v2/config-test-update-with-anonymous-access.json"); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + + request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); + response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"newPassword\"")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config")); + assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config")); + verify(userManager, never()).create(new User("_anonymous")); + } + @Test @SubjectAware(username = "readOnly") public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException { @@ -152,6 +198,7 @@ public class ConfigResourceTest { private static ScmConfiguration createConfiguration() { ScmConfiguration scmConfiguration = new ScmConfiguration(); scmConfiguration.setProxyPassword("heartOfGold"); + scmConfiguration.setAnonymousAccessEnabled(true); return scmConfiguration; } diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java index 0a37361f9c..e518578607 100644 --- a/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/SetupContextListenerTest.java @@ -11,6 +11,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import sonia.scm.config.ScmConfiguration; import sonia.scm.security.PermissionAssigner; import sonia.scm.security.PermissionDescriptor; import sonia.scm.user.User; @@ -23,7 +24,12 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class SetupContextListenerTest { @@ -40,12 +46,20 @@ class SetupContextListenerTest { @Mock private PasswordService passwordService; + @Mock + ScmConfiguration scmConfiguration; + @Mock private PermissionAssigner permissionAssigner; @InjectMocks private SetupContextListener.SetupAction setupAction; + @BeforeEach + void mockScmConfiguration() { + when(scmConfiguration.isAnonymousAccessEnabled()).thenReturn(false); + } + @BeforeEach void setupObjectUnderTest() { doAnswer(ic -> { @@ -90,6 +104,38 @@ class SetupContextListenerTest { verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any(Collection.class)); } + @Test + void shouldCreateAnonymousUserIfRequired() { + List users = Lists.newArrayList(UserTestData.createTrillian()); + when(userManager.getAll()).thenReturn(users); + when(scmConfiguration.isAnonymousAccessEnabled()).thenReturn(true); + + setupContextListener.contextInitialized(null); + + verify(userManager).create(new User("_anonymous")); + } + + @Test + void shouldNotCreateAnonymousUserIfNotRequired() { + List users = Lists.newArrayList(UserTestData.createTrillian()); + when(userManager.getAll()).thenReturn(users); + + setupContextListener.contextInitialized(null); + + verify(userManager, never()).create(new User("_anonymous")); + } + + @Test + void shouldNotCreateAnonymousUserIfAlreadyExists() { + List users = Lists.newArrayList(new User("_anonymous")); + when(userManager.getAll()).thenReturn(users); + when(scmConfiguration.isAnonymousAccessEnabled()).thenReturn(true); + + setupContextListener.contextInitialized(null); + + verify(userManager, times(1)).create(new User("_anonymous")); + } + private void verifyAdminPermissionsAssigned() { ArgumentCaptor usernameCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor> permissionCaptor = ArgumentCaptor.forClass(Collection.class); diff --git a/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-update-with-anonymous-access.json b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-update-with-anonymous-access.json new file mode 100644 index 0000000000..7be259d272 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-update-with-anonymous-access.json @@ -0,0 +1,4 @@ +{ + "proxyPassword": "newPassword", + "anonymousAccessEnabled": "true" +}