create _anonymous user when anonymous access activated and user does not exist yet / also create _anonymous user on system start if required

This commit is contained in:
Eduard Heimbuch
2019-10-09 15:45:32 +02:00
parent 964c9d2c8d
commit 8556278533
5 changed files with 119 additions and 4 deletions

View File

@@ -6,6 +6,8 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.config.ConfigurationPermissions; import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.NamespaceStrategyValidator; import sonia.scm.repository.NamespaceStrategyValidator;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.util.ScmConfigurationUtil; import sonia.scm.util.ScmConfigurationUtil;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
@@ -29,15 +31,17 @@ public class ConfigResource {
private final ScmConfigurationToConfigDtoMapper configToDtoMapper; private final ScmConfigurationToConfigDtoMapper configToDtoMapper;
private final ScmConfiguration configuration; private final ScmConfiguration configuration;
private final NamespaceStrategyValidator namespaceStrategyValidator; private final NamespaceStrategyValidator namespaceStrategyValidator;
private final UserManager userManager;
@Inject @Inject
public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper, public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper,
ScmConfigurationToConfigDtoMapper configToDtoMapper, ScmConfigurationToConfigDtoMapper configToDtoMapper,
ScmConfiguration configuration, NamespaceStrategyValidator namespaceStrategyValidator) { ScmConfiguration configuration, NamespaceStrategyValidator namespaceStrategyValidator, UserManager userManager) {
this.dtoToConfigMapper = dtoToConfigMapper; this.dtoToConfigMapper = dtoToConfigMapper;
this.configToDtoMapper = configToDtoMapper; this.configToDtoMapper = configToDtoMapper;
this.configuration = configuration; this.configuration = configuration;
this.namespaceStrategyValidator = namespaceStrategyValidator; this.namespaceStrategyValidator = namespaceStrategyValidator;
this.userManager = userManager;
} }
/** /**
@@ -92,6 +96,10 @@ public class ConfigResource {
ScmConfigurationUtil.getInstance().store(configuration); ScmConfigurationUtil.getInstance().store(configuration);
} }
if (config.isAnonymousAccessEnabled() && !userManager.contains("_anonymous")) {
userManager.create(new User("_anonymous"));
}
return Response.noContent().build(); return Response.noContent().build();
} }
} }

View File

@@ -4,6 +4,7 @@ import com.google.common.annotations.VisibleForTesting;
import org.apache.shiro.authc.credential.PasswordService; import org.apache.shiro.authc.credential.PasswordService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
import sonia.scm.security.PermissionAssigner; import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor; import sonia.scm.security.PermissionDescriptor;
@@ -47,12 +48,14 @@ public class SetupContextListener implements ServletContextListener {
private final UserManager userManager; private final UserManager userManager;
private final PasswordService passwordService; private final PasswordService passwordService;
private final PermissionAssigner permissionAssigner; private final PermissionAssigner permissionAssigner;
private final ScmConfiguration scmConfiguration;
@Inject @Inject
public SetupAction(UserManager userManager, PasswordService passwordService, PermissionAssigner permissionAssigner) { public SetupAction(UserManager userManager, PasswordService passwordService, PermissionAssigner permissionAssigner, ScmConfiguration scmConfiguration) {
this.userManager = userManager; this.userManager = userManager;
this.passwordService = passwordService; this.passwordService = passwordService;
this.permissionAssigner = permissionAssigner; this.permissionAssigner = permissionAssigner;
this.scmConfiguration = scmConfiguration;
} }
@Override @Override
@@ -60,6 +63,13 @@ public class SetupContextListener implements ServletContextListener {
if (isFirstStart()) { if (isFirstStart()) {
createAdminAccount(); createAdminAccount();
} }
if (anonymousUserRequiredButNotExists()) {
userManager.create(new User("_anonymous"));
}
}
private boolean anonymousUserRequiredButNotExists() {
return scmConfiguration.isAnonymousAccessEnabled() && !userManager.contains("_anonymous");
} }
private boolean isFirstStart() { private boolean isFirstStart() {

View File

@@ -16,6 +16,8 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.NamespaceStrategyValidator; import sonia.scm.repository.NamespaceStrategyValidator;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
import javax.servlet.http.HttpServletResponse; 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks; import static org.mockito.MockitoAnnotations.initMocks;
@SubjectAware( @SubjectAware(
@@ -50,6 +54,9 @@ public class ConfigResourceTest {
@SuppressWarnings("unused") // Is injected @SuppressWarnings("unused") // Is injected
private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@Mock
private UserManager userManager;
@Mock @Mock
private NamespaceStrategyValidator namespaceStrategyValidator; private NamespaceStrategyValidator namespaceStrategyValidator;
@@ -69,7 +76,7 @@ public class ConfigResourceTest {
public void prepareEnvironment() { public void prepareEnvironment() {
initMocks(this); initMocks(this);
ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration(), namespaceStrategyValidator); ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration(), namespaceStrategyValidator, userManager);
dispatcher.getRegistry().addSingletonResource(configResource); dispatcher.getRegistry().addSingletonResource(configResource);
} }
@@ -114,6 +121,45 @@ public class ConfigResourceTest {
assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config")); 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 @Test
@SubjectAware(username = "readOnly") @SubjectAware(username = "readOnly")
public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException { public void shouldNotUpdateConfigWhenNotAuthorized() throws URISyntaxException, IOException {
@@ -152,6 +198,7 @@ public class ConfigResourceTest {
private static ScmConfiguration createConfiguration() { private static ScmConfiguration createConfiguration() {
ScmConfiguration scmConfiguration = new ScmConfiguration(); ScmConfiguration scmConfiguration = new ScmConfiguration();
scmConfiguration.setProxyPassword("heartOfGold"); scmConfiguration.setProxyPassword("heartOfGold");
scmConfiguration.setAnonymousAccessEnabled(true);
return scmConfiguration; return scmConfiguration;
} }

View File

@@ -11,6 +11,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness; import org.mockito.quality.Strictness;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.PermissionAssigner; import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor; import sonia.scm.security.PermissionDescriptor;
import sonia.scm.user.User; import sonia.scm.user.User;
@@ -23,7 +24,12 @@ import java.util.List;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; 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) @ExtendWith(MockitoExtension.class)
class SetupContextListenerTest { class SetupContextListenerTest {
@@ -40,12 +46,20 @@ class SetupContextListenerTest {
@Mock @Mock
private PasswordService passwordService; private PasswordService passwordService;
@Mock
ScmConfiguration scmConfiguration;
@Mock @Mock
private PermissionAssigner permissionAssigner; private PermissionAssigner permissionAssigner;
@InjectMocks @InjectMocks
private SetupContextListener.SetupAction setupAction; private SetupContextListener.SetupAction setupAction;
@BeforeEach
void mockScmConfiguration() {
when(scmConfiguration.isAnonymousAccessEnabled()).thenReturn(false);
}
@BeforeEach @BeforeEach
void setupObjectUnderTest() { void setupObjectUnderTest() {
doAnswer(ic -> { doAnswer(ic -> {
@@ -90,6 +104,38 @@ class SetupContextListenerTest {
verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any(Collection.class)); verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any(Collection.class));
} }
@Test
void shouldCreateAnonymousUserIfRequired() {
List<User> 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<User> users = Lists.newArrayList(UserTestData.createTrillian());
when(userManager.getAll()).thenReturn(users);
setupContextListener.contextInitialized(null);
verify(userManager, never()).create(new User("_anonymous"));
}
@Test
void shouldNotCreateAnonymousUserIfAlreadyExists() {
List<User> 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() { private void verifyAdminPermissionsAssigned() {
ArgumentCaptor<String> usernameCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor<String> usernameCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Collection<PermissionDescriptor>> permissionCaptor = ArgumentCaptor.forClass(Collection.class); ArgumentCaptor<Collection<PermissionDescriptor>> permissionCaptor = ArgumentCaptor.forClass(Collection.class);

View File

@@ -0,0 +1,4 @@
{
"proxyPassword": "newPassword",
"anonymousAccessEnabled": "true"
}