mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-07-04 05:28:40 +02:00
Create custom initial user (#1707)
Using a default user with a default password has the implicit risk, that this user is not changed and therefore this system can be compromised. With this change, SCM-Manager does not create the default user with the default password on startup any more, but it shows an initial form where the initial values for the administration user have to be entered by the user. To secure this form, a random token is created on startup and printed in the log. To implement this form, the concept of an InitializationStep is introduced. This extension point can be implemented to offer different setup tasks. The creation of the administration user is the first implementation, others might be things like first plugin selections or the like. Frontend components are selected by the name of these initialization steps, whose names will be added to the index resource (whichever is active at the moment) and will be show accordingly. Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.lifecycle.AdminAccountStartupAction;
|
||||
import sonia.scm.web.RestDispatcher;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Collections.singleton;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.jboss.resteasy.mock.MockHttpRequest.post;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AdminAccountStartupResourceTest {
|
||||
|
||||
private final RestDispatcher dispatcher = new RestDispatcher();
|
||||
private final MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
@Mock
|
||||
private AdminAccountStartupAction startupAction;
|
||||
@Mock
|
||||
private Provider<ScmPathInfoStore> pathInfoStoreProvider;
|
||||
@Mock
|
||||
private ScmPathInfoStore pathInfoStore;
|
||||
@Mock
|
||||
private ScmPathInfo pathInfo;
|
||||
|
||||
@InjectMocks
|
||||
private AdminAccountStartupResource resource;
|
||||
|
||||
@BeforeEach
|
||||
void setUpMocks() {
|
||||
lenient().when(pathInfoStoreProvider.get()).thenReturn(pathInfoStore);
|
||||
lenient().when(pathInfoStore.get()).thenReturn(pathInfo);
|
||||
dispatcher.addSingletonResource(new InitializationResource(singleton(resource)));
|
||||
lenient().when(startupAction.name()).thenReturn("adminAccount");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWhenActionIsDone() throws URISyntaxException {
|
||||
when(startupAction.done()).thenReturn(true);
|
||||
|
||||
MockHttpRequest request =
|
||||
post("/v2/initialization/adminAccount")
|
||||
.contentType("application/json")
|
||||
.content(createInput("irrelevant", "irrelevant", "irrelevant", "irrelevant@some.com", "irrelevant", "irrelevant"));
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(400);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithNecessaryAction {
|
||||
|
||||
@BeforeEach
|
||||
void actionNotDone() {
|
||||
when(startupAction.done()).thenReturn(false);
|
||||
when(startupAction.isCorrectToken(any())).thenAnswer(i -> "initial-token".equals(i.getArgument(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithWrongToken() throws URISyntaxException {
|
||||
MockHttpRequest request =
|
||||
post("/v2/initialization/adminAccount")
|
||||
.contentType("application/json")
|
||||
.content(createInput("wrong-token", "trillian", "Tricia", "tricia@hitchhiker.com", "something", "different"));
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWhenPasswordsAreNotEqual() throws URISyntaxException {
|
||||
MockHttpRequest request =
|
||||
post("/v2/initialization/adminAccount")
|
||||
.contentType("application/json")
|
||||
.content(createInput("initial-token", "trillian", "Tricia", "tricia@hitchhiker.com", "something", "different"));
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(400);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateAdminUser() throws URISyntaxException {
|
||||
MockHttpRequest request =
|
||||
post("/v2/initialization/adminAccount")
|
||||
.contentType("application/json")
|
||||
.content(createInput("initial-token", "trillian", "Tricia", "tricia@hitchhiker.com", "password", "password"));
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
|
||||
verify(startupAction).createAdminUser("trillian", "Tricia", "tricia@hitchhiker.com", "password");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] createInput(String token, String userName, String displayName, String email, String password, String confirmation) {
|
||||
return json(format("{'startupToken': '%s', 'userName': '%s', 'displayName': '%s', 'email': '%s', 'password': '%s', 'passwordConfirmation': '%s'}", token, userName, displayName, email, password, confirmation));
|
||||
}
|
||||
|
||||
private byte[] json(String s) {
|
||||
return s.replaceAll("'", "\"").getBytes(UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,13 @@
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
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.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -35,11 +38,18 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.BasicContextProvider;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.initialization.InitializationFinisher;
|
||||
import sonia.scm.initialization.InitializationStep;
|
||||
import sonia.scm.initialization.InitializationStepResource;
|
||||
import sonia.scm.security.AnonymousMode;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Link.link;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.SCMContext.USER_ANONYMOUS;
|
||||
|
||||
@@ -49,80 +59,130 @@ class IndexDtoGeneratorTest {
|
||||
private static final ScmPathInfo scmPathInfo = () -> URI.create("/api/v2");
|
||||
|
||||
@Mock
|
||||
private ScmConfiguration configuration;
|
||||
private ResourceLinks resourceLinks;
|
||||
@Mock
|
||||
private BasicContextProvider contextProvider;
|
||||
@Mock
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
private ScmConfiguration configuration;
|
||||
@Mock
|
||||
private Subject subject;
|
||||
private InitializationFinisher initializationFinisher;
|
||||
|
||||
@InjectMocks
|
||||
private IndexDtoGenerator generator;
|
||||
|
||||
@BeforeEach
|
||||
void bindSubject() {
|
||||
ThreadContext.bind(subject);
|
||||
@Nested
|
||||
class WithFullyInitializedSystem {
|
||||
|
||||
@Mock
|
||||
private Subject subject;
|
||||
|
||||
@BeforeEach
|
||||
void fullyInitialized() {
|
||||
when(initializationFinisher.isFullyInitialized()).thenReturn(true);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void bindSubject() {
|
||||
ThreadContext.bind(subject);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDownSubject() {
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendMeIfAuthenticated() {
|
||||
mockSubjectRelatedResourceLinks();
|
||||
when(subject.isAuthenticated()).thenReturn(true);
|
||||
|
||||
when(contextProvider.getVersion()).thenReturn("2.x");
|
||||
|
||||
IndexDto dto = generator.generate();
|
||||
|
||||
assertThat(dto.getLinks().getLinkBy("me")).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendMeIfUserIsAuthenticatedButAnonymous() {
|
||||
mockResourceLinks();
|
||||
when(subject.getPrincipal()).thenReturn(USER_ANONYMOUS);
|
||||
when(subject.isAuthenticated()).thenReturn(true);
|
||||
|
||||
IndexDto dto = generator.generate();
|
||||
|
||||
assertThat(dto.getLinks().getLinkBy("me")).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendMeIfUserIsAnonymousAndAnonymousModeIsFullEnabled() {
|
||||
mockSubjectRelatedResourceLinks();
|
||||
when(subject.getPrincipal()).thenReturn(USER_ANONYMOUS);
|
||||
when(subject.isAuthenticated()).thenReturn(true);
|
||||
when(configuration.getAnonymousMode()).thenReturn(AnonymousMode.FULL);
|
||||
|
||||
IndexDto dto = generator.generate();
|
||||
|
||||
assertThat(dto.getLinks().getLinkBy("me")).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendMeIfUserIsAnonymousAndAnonymousModeIsProtocolOnly() {
|
||||
mockResourceLinks();
|
||||
when(subject.getPrincipal()).thenReturn(USER_ANONYMOUS);
|
||||
when(subject.isAuthenticated()).thenReturn(true);
|
||||
when(configuration.getAnonymousMode()).thenReturn(AnonymousMode.PROTOCOL_ONLY);
|
||||
|
||||
IndexDto dto = generator.generate();
|
||||
|
||||
assertThat(dto.getLinks().getLinkBy("me")).isNotPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDownSubject() {
|
||||
ThreadContext.unbindSubject();
|
||||
@Nested
|
||||
class WithUnfinishedInitialization {
|
||||
|
||||
@Mock
|
||||
private InitializationStep initializationStep;
|
||||
@Mock
|
||||
private InitializationStepResource initializationStepResource;
|
||||
|
||||
@Test
|
||||
void shouldCreateInitializationLink() {
|
||||
mockBaseLink();
|
||||
when(initializationFinisher.isFullyInitialized()).thenReturn(false);
|
||||
when(initializationFinisher.missingInitialization()).thenReturn(initializationStep);
|
||||
when(initializationStep.name()).thenReturn("probability");
|
||||
when(initializationFinisher.getResource("probability")).thenReturn(initializationStepResource);
|
||||
|
||||
doAnswer(invocationOnMock -> {
|
||||
Links.Builder initializationLinkBuilder = invocationOnMock.getArgument(0, Links.Builder.class);
|
||||
Embedded.Builder initializationEmbeddedBuilder = invocationOnMock.getArgument(1, Embedded.Builder.class);
|
||||
initializationLinkBuilder.single(link("init", "/init"));
|
||||
return null;
|
||||
}).when(initializationStepResource).setupIndex(any(), any());
|
||||
|
||||
IndexDto dto = generator.generate();
|
||||
|
||||
assertThat(dto.getInitialization()).isEqualTo("probability");
|
||||
List<InitializationDto> initializationDtos = dto.getEmbedded().getItemsBy("probability", InitializationDto.class);
|
||||
assertThat(initializationDtos).hasSize(1).allMatch(
|
||||
initializationDto -> {
|
||||
assertThat(initializationDto.getLinks().hasLink("init")).isTrue();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendMeIfAuthenticated() {
|
||||
mockSubjectRelatedResourceLinks();
|
||||
when(subject.isAuthenticated()).thenReturn(true);
|
||||
|
||||
when(contextProvider.getVersion()).thenReturn("2.x");
|
||||
|
||||
IndexDto dto = generator.generate();
|
||||
|
||||
assertThat(dto.getLinks().getLinkBy("me")).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendMeIfUserIsAuthenticatedButAnonymous() {
|
||||
mockResourceLinks();
|
||||
when(subject.getPrincipal()).thenReturn(USER_ANONYMOUS);
|
||||
when(subject.isAuthenticated()).thenReturn(true);
|
||||
|
||||
IndexDto dto = generator.generate();
|
||||
|
||||
assertThat(dto.getLinks().getLinkBy("me")).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendMeIfUserIsAnonymousAndAnonymousModeIsFullEnabled() {
|
||||
mockSubjectRelatedResourceLinks();
|
||||
when(subject.getPrincipal()).thenReturn(USER_ANONYMOUS);
|
||||
when(subject.isAuthenticated()).thenReturn(true);
|
||||
when(configuration.getAnonymousMode()).thenReturn(AnonymousMode.FULL);
|
||||
|
||||
IndexDto dto = generator.generate();
|
||||
|
||||
assertThat(dto.getLinks().getLinkBy("me")).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendMeIfUserIsAnonymousAndAnonymousModeIsProtocolOnly() {
|
||||
mockResourceLinks();
|
||||
when(subject.getPrincipal()).thenReturn(USER_ANONYMOUS);
|
||||
when(subject.isAuthenticated()).thenReturn(true);
|
||||
when(configuration.getAnonymousMode()).thenReturn(AnonymousMode.PROTOCOL_ONLY);
|
||||
|
||||
IndexDto dto = generator.generate();
|
||||
|
||||
assertThat(dto.getLinks().getLinkBy("me")).isNotPresent();
|
||||
}
|
||||
|
||||
|
||||
private void mockResourceLinks() {
|
||||
mockBaseLink();
|
||||
when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(scmPathInfo));
|
||||
}
|
||||
|
||||
private void mockBaseLink() {
|
||||
when(resourceLinks.index()).thenReturn(new ResourceLinks.IndexLinks(scmPathInfo));
|
||||
when(resourceLinks.uiPluginCollection()).thenReturn(new ResourceLinks.UIPluginCollectionLinks(scmPathInfo));
|
||||
when(resourceLinks.authentication()).thenReturn(new ResourceLinks.AuthenticationLinks(scmPathInfo));
|
||||
}
|
||||
|
||||
private void mockSubjectRelatedResourceLinks() {
|
||||
|
||||
@@ -32,6 +32,9 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.initialization.InitializationFinisher;
|
||||
import sonia.scm.initialization.InitializationStep;
|
||||
import sonia.scm.initialization.InitializationStepResource;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
@@ -54,11 +57,13 @@ public class IndexResourceTest {
|
||||
public void setUpObjectUnderTest() {
|
||||
this.configuration = new ScmConfiguration();
|
||||
this.scmContextProvider = mock(SCMContextProvider.class);
|
||||
InitializationFinisher initializationFinisher = mock(InitializationFinisher.class);
|
||||
when(initializationFinisher.isFullyInitialized()).thenReturn(true);
|
||||
IndexDtoGenerator generator = new IndexDtoGenerator(
|
||||
ResourceLinksMock.createMock(URI.create("/")),
|
||||
scmContextProvider,
|
||||
configuration
|
||||
);
|
||||
configuration,
|
||||
initializationFinisher);
|
||||
this.indexResource = new IndexResource(generator);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,11 @@ package sonia.scm.lifecycle;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
@@ -40,13 +41,17 @@ import sonia.scm.security.PermissionDescriptor;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
import sonia.scm.user.UserTestData;
|
||||
import sonia.scm.web.security.AdministrationContext;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -60,30 +65,89 @@ class AdminAccountStartupActionTest {
|
||||
private UserManager userManager;
|
||||
@Mock
|
||||
private PermissionAssigner permissionAssigner;
|
||||
@Mock
|
||||
private RandomPasswordGenerator randomPasswordGenerator;
|
||||
@Mock
|
||||
private AdministrationContext context;
|
||||
|
||||
@InjectMocks
|
||||
private AdminAccountStartupAction startupAction;
|
||||
AdminAccountStartupAction startupAction;
|
||||
|
||||
@Test
|
||||
void shouldCreateAdminAccountIfNoUserExistsAndAssignPermissions() {
|
||||
when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
|
||||
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||
|
||||
startupAction.run();
|
||||
@BeforeEach
|
||||
void clearProperties() {
|
||||
System.clearProperty("scm.initialPassword");
|
||||
System.clearProperty("sonia.scm.skipAdminCreation");
|
||||
|
||||
verifyAdminCreated();
|
||||
verifyAdminPermissionsAssigned();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void mockAdminContext() {
|
||||
doAnswer(invocation -> {
|
||||
invocation.getArgument(0, PrivilegedStartupAction.class).run();
|
||||
return null;
|
||||
}).when(context).runAsAdmin(any(PrivilegedStartupAction.class));
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUpUserCaptor() {
|
||||
lenient().when(userManager.create(userCaptor.capture())).thenAnswer(i -> i.getArgument(0));
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WithPredefinedPassword {
|
||||
@BeforeEach
|
||||
void initPasswordGenerator() {
|
||||
System.setProperty("scm.initialPassword", "password");
|
||||
lenient().when(passwordService.encryptPassword("password")).thenReturn("encrypted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateAdminAccountIfNoUserExistsAndAssignPermissions() {
|
||||
createStartupAction();
|
||||
|
||||
verifyAdminCreated();
|
||||
verifyAdminPermissionsAssigned();
|
||||
assertThat(startupAction.done()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateAdminAccountIfOnlyAnonymousUserExistsAndAssignPermissions() {
|
||||
when(userManager.getAll()).thenReturn(Lists.newArrayList(SCMContext.ANONYMOUS));
|
||||
when(userManager.contains(SCMContext.USER_ANONYMOUS)).thenReturn(true);
|
||||
|
||||
createStartupAction();
|
||||
|
||||
verifyAdminCreated();
|
||||
verifyAdminPermissionsAssigned();
|
||||
assertThat(startupAction.done()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDoNothingOnSecondStart() {
|
||||
List<User> users = Lists.newArrayList(UserTestData.createTrillian());
|
||||
when(userManager.getAll()).thenReturn(users);
|
||||
|
||||
createStartupAction();
|
||||
|
||||
verify(userManager, never()).create(any(User.class));
|
||||
verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any());
|
||||
assertThat(startupAction.done()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateAdminAccountIfOnlyAnonymousUserExistsAndAssignPermissions() {
|
||||
when(userManager.getAll()).thenReturn(Lists.newArrayList(SCMContext.ANONYMOUS));
|
||||
when(userManager.contains(SCMContext.USER_ANONYMOUS)).thenReturn(true);
|
||||
when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
|
||||
void shouldCreateStartupToken() {
|
||||
lenient().when(randomPasswordGenerator.createRandomPassword()).thenReturn("random");
|
||||
when(userManager.getAll()).thenReturn(Collections.emptyList());
|
||||
|
||||
startupAction.run();
|
||||
createStartupAction();
|
||||
|
||||
verifyAdminCreated();
|
||||
verifyAdminPermissionsAssigned();
|
||||
verify(userManager, never()).create(any(User.class));
|
||||
verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any());
|
||||
assertThat(startupAction.done()).isFalse();
|
||||
assertThat(startupAction.isCorrectToken("random")).isTrue();
|
||||
assertThat(startupAction.isCorrectToken("wrong")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -91,14 +155,10 @@ class AdminAccountStartupActionTest {
|
||||
void shouldSkipAdminAccountCreationIfPropertyIsSet() {
|
||||
System.setProperty("sonia.scm.skipAdminCreation", "true");
|
||||
|
||||
try {
|
||||
startupAction.run();
|
||||
createStartupAction();
|
||||
|
||||
verify(userManager, never()).create(any());
|
||||
verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any(Collection.class));
|
||||
} finally {
|
||||
System.setProperty("sonia.scm.skipAdminCreation", "");
|
||||
}
|
||||
verify(userManager, never()).create(any());
|
||||
verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -106,10 +166,15 @@ class AdminAccountStartupActionTest {
|
||||
List<User> users = Lists.newArrayList(UserTestData.createTrillian());
|
||||
when(userManager.getAll()).thenReturn(users);
|
||||
|
||||
startupAction.run();
|
||||
createStartupAction();
|
||||
|
||||
verify(userManager, never()).create(any(User.class));
|
||||
verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any(Collection.class));
|
||||
verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any());
|
||||
assertThat(startupAction.done()).isTrue();
|
||||
}
|
||||
|
||||
private void createStartupAction() {
|
||||
startupAction = new AdminAccountStartupAction(passwordService, userManager, permissionAssigner, randomPasswordGenerator, context);
|
||||
}
|
||||
|
||||
private void verifyAdminPermissionsAssigned() {
|
||||
@@ -123,10 +188,8 @@ class AdminAccountStartupActionTest {
|
||||
}
|
||||
|
||||
private void verifyAdminCreated() {
|
||||
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||
verify(userManager).create(userCaptor.capture());
|
||||
User user = userCaptor.getValue();
|
||||
assertThat(user.getName()).isEqualTo("scmadmin");
|
||||
assertThat(user.getPassword()).isEqualTo("secret");
|
||||
assertThat(user.getPassword()).isEqualTo("encrypted");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.security;
|
||||
|
||||
import org.apache.shiro.subject.Subject;
|
||||
@@ -52,7 +52,7 @@ import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class JwtAccessTokenRefresherTest {
|
||||
class JwtAccessTokenRefresherTest {
|
||||
|
||||
private static final Instant NOW = Instant.now().truncatedTo(SECONDS);
|
||||
private static final Instant TOKEN_CREATION = NOW.minus(ofMinutes(1));
|
||||
@@ -182,4 +182,16 @@ public class JwtAccessTokenRefresherTest {
|
||||
JwtAccessToken refreshedToken = refreshedTokenResult.get();
|
||||
assertThat(refreshedToken.getRefreshExpiration()).get().isEqualTo(Date.from(TOKEN_CREATION.plus(ofMinutes(10))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRefreshTokenWhenPrincipalIsMissing() {
|
||||
JwtAccessToken oldToken = tokenBuilder.build();
|
||||
|
||||
when(subject.getPrincipals()).thenReturn(null);
|
||||
|
||||
Optional<JwtAccessToken> refreshedTokenResult = refresher.refresh(oldToken);
|
||||
|
||||
assertThat(refreshedTokenResult).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user