mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-05-06 07:46:55 +02:00
Merge with upstream
This commit is contained in:
@@ -46,8 +46,6 @@ public class ManagerDaoAdapter<T extends ModelObject> {
|
||||
if (notModified != null) {
|
||||
permissionCheck.apply(notModified).check();
|
||||
|
||||
doThrow().violation("type must not be changed").when(!notModified.getType().equals(object.getType()));
|
||||
|
||||
AssertUtil.assertIsValid(object);
|
||||
|
||||
beforeUpdate.handle(notModified);
|
||||
|
||||
@@ -35,16 +35,14 @@ import org.hibernate.validator.constraints.Length;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@SuppressWarnings("java:S2160") // we do not need this for dto
|
||||
public class BranchDto extends HalRepresentation {
|
||||
|
||||
private static final String VALID_CHARACTERS_AT_START_AND_END = "\\w-,;\\]{}@&+=$#`|<>";
|
||||
private static final String VALID_CHARACTERS = VALID_CHARACTERS_AT_START_AND_END + "/.";
|
||||
static final String VALID_BRANCH_NAMES = "[" + VALID_CHARACTERS_AT_START_AND_END + "]([" + VALID_CHARACTERS + "]*[" + VALID_CHARACTERS_AT_START_AND_END + "])?";
|
||||
|
||||
@NotEmpty
|
||||
@Length(min = 1, max = 100)
|
||||
@Pattern(regexp = VALID_BRANCH_NAMES)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import lombok.Getter;
|
||||
@@ -31,7 +31,7 @@ import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import static sonia.scm.api.v2.resources.BranchDto.VALID_BRANCH_NAMES;
|
||||
import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
@@ -56,6 +56,7 @@ public class ConfigDto extends HalRepresentation {
|
||||
private String pluginUrl;
|
||||
private long loginAttemptLimitTimeout;
|
||||
private boolean enabledXsrfProtection;
|
||||
private boolean enabledUserConverter;
|
||||
private String namespaceStrategy;
|
||||
private String loginInfoUrl;
|
||||
private String releaseFeedUrl;
|
||||
|
||||
@@ -102,7 +102,7 @@ public class MeDtoFactory extends HalAppenderMapper {
|
||||
if (UserPermissions.changePublicKeys(user).isPermitted()) {
|
||||
linksBuilder.single(link("publicKeys", resourceLinks.user().publicKeys(user.getName())));
|
||||
}
|
||||
if (userManager.isTypeDefault(user) && UserPermissions.changePassword(user).isPermitted()) {
|
||||
if (!user.isExternal() && UserPermissions.changePassword(user).isPermitted()) {
|
||||
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
||||
}
|
||||
if (UserPermissions.changeApiKeys(user).isPermitted()) {
|
||||
|
||||
@@ -123,6 +123,14 @@ class ResourceLinks {
|
||||
return userLinkBuilder.method("getUserResource").parameters(name).method("overwritePassword").parameters().href();
|
||||
}
|
||||
|
||||
public String toExternal(String name) {
|
||||
return userLinkBuilder.method("getUserResource").parameters(name).method("toExternal").parameters().href();
|
||||
}
|
||||
|
||||
public String toInternal(String name) {
|
||||
return userLinkBuilder.method("getUserResource").parameters(name).method("toInternal").parameters().href();
|
||||
}
|
||||
|
||||
public String publicKeys(String name) {
|
||||
return publicKeyLinkBuilder.method("findAll").parameters(name).href();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import java.time.Instant;
|
||||
@NoArgsConstructor @Getter @Setter
|
||||
public class UserDto extends HalRepresentation {
|
||||
private boolean active;
|
||||
private boolean external;
|
||||
private Instant creationDate;
|
||||
@NotEmpty
|
||||
private String displayName;
|
||||
|
||||
@@ -186,6 +186,71 @@ public class UserResource {
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* This Endpoint is for Admin user to convert external user to internal.
|
||||
* The oldPassword property of the DTO is not needed here. it will be ignored.
|
||||
* The oldPassword property is needed in the MeResources when the actual user change the own password.
|
||||
*
|
||||
* <strong>Note:</strong> This method requires "user:modify" privilege to modify the password of other users.
|
||||
*
|
||||
* @param name name of the user to be modified
|
||||
* @param passwordOverwrite change password object to modify password. the old password is here not required
|
||||
*/
|
||||
@PUT
|
||||
@Path("convert-to-internal")
|
||||
@Consumes(VndMediaType.USER)
|
||||
@Operation(summary = "Converts an external user to internal", description = "Converts an external user to an internal one and set the new password.", tags = "User")
|
||||
@ApiResponse(responseCode = "204", description = "update success")
|
||||
@ApiResponse(responseCode = "400", description = "invalid body, e.g. the new password is missing")
|
||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege")
|
||||
@ApiResponse(
|
||||
responseCode = "404",
|
||||
description = "not found, no user with the specified id/name available",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
@ApiResponse(responseCode = "500", description = "internal server error")
|
||||
public Response toInternal(@PathParam("id") String name, @Valid PasswordOverwriteDto passwordOverwrite) {
|
||||
UserDto dto = userToDtoMapper.map(userManager.get(name));
|
||||
dto.setExternal(false);
|
||||
adapter.update(name, existing -> dtoToUserMapper.map(dto, existing.getPassword()));
|
||||
userManager.overwritePassword(name, passwordService.encryptPassword(passwordOverwrite.getNewPassword()));
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* This Endpoint is for Admin user to convert internal user to external.
|
||||
*
|
||||
* <strong>Note:</strong> This method requires "user:modify" privilege to modify the password of other users.
|
||||
*
|
||||
* @param name name of the user to be modified
|
||||
*/
|
||||
@PUT
|
||||
@Path("convert-to-external")
|
||||
@Consumes(VndMediaType.USER)
|
||||
@Operation(summary = "Converts an internal user to external", description = "Converts an internal user to an external one and removes the local password.", tags = "User")
|
||||
@ApiResponse(responseCode = "204", description = "update success")
|
||||
@ApiResponse(responseCode = "400", description = "invalid body, e.g. the new password is missing")
|
||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"user\" privilege")
|
||||
@ApiResponse(
|
||||
responseCode = "404",
|
||||
description = "not found, no user with the specified id/name available",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
@ApiResponse(responseCode = "500", description = "internal server error")
|
||||
public Response toExternal(@PathParam("id") String name) {
|
||||
userManager.overwritePassword(name, null);
|
||||
UserDto dto = userToDtoMapper.map(userManager.get(name));
|
||||
dto.setExternal(true);
|
||||
adapter.update(name, existing -> dtoToUserMapper.map(dto, existing.getPassword()));
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@Path("permissions")
|
||||
public UserPermissionResource permissions() {
|
||||
return userPermissionResource;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
@@ -66,8 +66,11 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
||||
if (UserPermissions.modify(user).isPermitted()) {
|
||||
linksBuilder.single(link("update", resourceLinks.user().update(user.getName())));
|
||||
linksBuilder.single(link("publicKeys", resourceLinks.user().publicKeys(user.getName())));
|
||||
if (userManager.isTypeDefault(user)) {
|
||||
if (user.isExternal()) {
|
||||
linksBuilder.single(link("convertToInternal", resourceLinks.user().toInternal(user.getName())));
|
||||
} else {
|
||||
linksBuilder.single(link("password", resourceLinks.user().passwordChange(user.getName())));
|
||||
linksBuilder.single(link("convertToExternal", resourceLinks.user().toExternal(user.getName())));
|
||||
}
|
||||
}
|
||||
if (PermissionPermissions.read().isPermitted()) {
|
||||
|
||||
@@ -389,8 +389,8 @@ public class DefaultUserManager extends AbstractUserManager
|
||||
if (user == null) {
|
||||
throw new NotFoundException(User.class, userId);
|
||||
}
|
||||
if (!isTypeDefault(user) || isAnonymousUser(user)) {
|
||||
throw new ChangePasswordNotAllowedException(ContextEntry.ContextBuilder.entity("PasswordChange", "-").in(User.class, user.getName()), user.getType());
|
||||
if (isAnonymousUser(user) || user.isExternal()) {
|
||||
throw new ChangePasswordNotAllowedException(ContextEntry.ContextBuilder.entity("PasswordChange", "-").in(User.class, user.getName()), "external");
|
||||
}
|
||||
user.setPassword(newPassword);
|
||||
this.modify(user);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.user;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Slf4j
|
||||
@Extension
|
||||
public class InternalToExternalUserConverter implements ExternalUserConverter{
|
||||
|
||||
private final ScmConfiguration scmConfiguration;
|
||||
|
||||
@Inject
|
||||
public InternalToExternalUserConverter(ScmConfiguration scmConfiguration) {
|
||||
this.scmConfiguration = scmConfiguration;
|
||||
}
|
||||
|
||||
public User convert(User user) {
|
||||
if (shouldConvertUser(user)) {
|
||||
log.info("Convert internal user {} to external", user.getId());
|
||||
user.setExternal(true);
|
||||
user.setPassword(null);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private boolean shouldConvertUser(User user) {
|
||||
return !user.isExternal() && scmConfiguration.isEnabledUserConverter();
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public class BrowserUserAgentProvider implements UserAgentProvider
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final UserAgent CHROME = UserAgent.builder(
|
||||
static final UserAgent CHROME = UserAgent.browser(
|
||||
"Chrome").basicAuthenticationCharset(
|
||||
Charsets.UTF_8).build();
|
||||
|
||||
@@ -50,21 +50,21 @@ public class BrowserUserAgentProvider implements UserAgentProvider
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final UserAgent FIREFOX = UserAgent.builder("Firefox").build();
|
||||
static final UserAgent FIREFOX = UserAgent.browser("Firefox").build();
|
||||
|
||||
/** Field description */
|
||||
private static final String FIREFOX_PATTERN = "firefox";
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final UserAgent MSIE = UserAgent.builder("Internet Explorer").build();
|
||||
static final UserAgent MSIE = UserAgent.browser("Internet Explorer").build();
|
||||
|
||||
/** Field description */
|
||||
private static final String MSIE_PATTERN = "msie";
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting // todo check charset
|
||||
static final UserAgent SAFARI = UserAgent.builder("Safari").build();
|
||||
static final UserAgent SAFARI = UserAgent.browser("Safari").build();
|
||||
|
||||
/** Field description */
|
||||
private static final String OPERA_PATTERN = "opera";
|
||||
@@ -74,7 +74,7 @@ public class BrowserUserAgentProvider implements UserAgentProvider
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting // todo check charset
|
||||
static final UserAgent OPERA = UserAgent.builder(
|
||||
static final UserAgent OPERA = UserAgent.browser(
|
||||
"Opera").basicAuthenticationCharset(
|
||||
Charsets.UTF_8).build();
|
||||
|
||||
|
||||
@@ -74,10 +74,7 @@ public class HttpProtocolServlet extends HttpServlet {
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
|
||||
UserAgent userAgent = userAgentParser.parse(request);
|
||||
if (userAgent.isBrowser()) {
|
||||
log.trace("dispatch browser request for user agent {}", userAgent);
|
||||
dispatcher.dispatch(request, response, request.getRequestURI());
|
||||
} else {
|
||||
if (userAgent.isScmClient()) {
|
||||
String pathInfo = request.getPathInfo();
|
||||
Optional<NamespaceAndName> namespaceAndName = pathExtractor.fromUri(pathInfo);
|
||||
if (namespaceAndName.isPresent()) {
|
||||
@@ -86,6 +83,9 @@ public class HttpProtocolServlet extends HttpServlet {
|
||||
log.debug("namespace and name not found in request path {}", pathInfo);
|
||||
response.setStatus(HttpStatus.SC_BAD_REQUEST);
|
||||
}
|
||||
} else {
|
||||
log.trace("dispatch non-scm-client request for user agent {}", userAgent);
|
||||
dispatcher.dispatch(request, response, request.getRequestURI());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* 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.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -29,6 +29,7 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
|
||||
|
||||
class BranchDtoTest {
|
||||
|
||||
@@ -54,10 +55,11 @@ class BranchDtoTest {
|
||||
"val{d",
|
||||
"val{}d",
|
||||
"val|kill",
|
||||
"val}"
|
||||
"val}",
|
||||
"va/li/d"
|
||||
})
|
||||
void shouldAcceptValidBranchName(String branchName) {
|
||||
assertTrue(branchName.matches(BranchDto.VALID_BRANCH_NAMES));
|
||||
assertTrue(branchName.matches(VALID_BRANCH_NAMES));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@@ -70,6 +72,6 @@ class BranchDtoTest {
|
||||
"val id"
|
||||
})
|
||||
void shouldRejectInvalidBranchName(String branchName) {
|
||||
assertFalse(branchName.matches(BranchDto.VALID_BRANCH_NAMES));
|
||||
assertFalse(branchName.matches(VALID_BRANCH_NAMES));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import sonia.scm.security.AnonymousMode;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
@@ -42,9 +43,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
||||
@InjectMocks
|
||||
private ConfigDtoToScmConfigurationMapperImpl mapper;
|
||||
|
||||
private String[] expectedUsers = {"trillian", "arthur"};
|
||||
private String[] expectedGroups = {"admin", "plebs"};
|
||||
private String[] expectedExcludes = {"ex", "clude"};
|
||||
private final String[] expectedExcludes = {"ex", "clude"};
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
@@ -73,6 +72,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
||||
assertEquals("https://plug.ins", config.getPluginUrl());
|
||||
assertEquals(40, config.getLoginAttemptLimitTimeout());
|
||||
assertTrue(config.isEnabledXsrfProtection());
|
||||
assertFalse(config.isEnabledUserConverter());
|
||||
assertEquals("username", config.getNamespaceStrategy());
|
||||
assertEquals("https://scm-manager.org/login-info", config.getLoginInfoUrl());
|
||||
assertEquals("hitchhiker.mail", config.getMailDomainName());
|
||||
@@ -115,6 +115,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
||||
configDto.setNamespaceStrategy("username");
|
||||
configDto.setLoginInfoUrl("https://scm-manager.org/login-info");
|
||||
configDto.setMailDomainName("hitchhiker.mail");
|
||||
configDto.setEnabledUserConverter(false);
|
||||
|
||||
return configDto;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import sonia.scm.ContextEntry;
|
||||
import sonia.scm.group.GroupCollector;
|
||||
import sonia.scm.security.ApiKey;
|
||||
import sonia.scm.security.ApiKeyService;
|
||||
import sonia.scm.user.EMail;
|
||||
import sonia.scm.user.InvalidPasswordException;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.user.UserManager;
|
||||
@@ -96,6 +97,9 @@ public class MeResourceTest {
|
||||
@Mock
|
||||
private ApiKeyService apiKeyService;
|
||||
|
||||
@Mock
|
||||
private EMail eMail;
|
||||
|
||||
@InjectMocks
|
||||
private MeDtoFactory meDtoFactory;
|
||||
@InjectMocks
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.inject.util.Providers;
|
||||
import com.sun.mail.iap.Argument;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
@@ -58,10 +59,12 @@ import java.util.Collection;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
@@ -457,6 +460,43 @@ public class UserRootResourceTest {
|
||||
assertEquals("other:*", captor.getValue().iterator().next().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldConvertUserToInternalAndSetNewPassword() throws URISyntaxException {
|
||||
when(passwordService.encryptPassword(anyString())).thenReturn("abc");
|
||||
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.put("/" + UserRootResource.USERS_PATH_V2 + "Neo/convert-to-internal")
|
||||
.contentType(VndMediaType.USER)
|
||||
.content("{\"newPassword\":\"trillian\"}".getBytes());
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
verify(passwordService).encryptPassword("trillian");
|
||||
verify(userManager).overwritePassword("Neo", "abc");
|
||||
verify(userManager).modify(userCaptor.capture());
|
||||
|
||||
User user = userCaptor.getValue();
|
||||
assertThat(user.isExternal()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldConvertUserToExternalAndRemoveLocalPassword() throws URISyntaxException {
|
||||
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.put("/" + UserRootResource.USERS_PATH_V2 + "Neo/convert-to-external")
|
||||
.contentType(VndMediaType.USER);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
verify(userManager).overwritePassword("Neo", null);
|
||||
verify(userManager).modify(userCaptor.capture());
|
||||
|
||||
User user = userCaptor.getValue();
|
||||
assertThat(user.isExternal()).isTrue();
|
||||
}
|
||||
|
||||
private PageResult<User> createSingletonPageResult(int overallCount) {
|
||||
return new PageResult<>(singletonList(createDummyUser("Neo")), overallCount);
|
||||
}
|
||||
|
||||
@@ -90,25 +90,15 @@ public class UserToUserDtoMapperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetPasswordLinkForAdmin() {
|
||||
public void shouldGetInternalUserLinks() {
|
||||
User user = createDefaultUser();
|
||||
user.setExternal(false);
|
||||
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||
when(userManager.isTypeDefault(eq(user))).thenReturn(true);
|
||||
|
||||
UserDto userDto = mapper.map(user);
|
||||
|
||||
assertEquals("expected password link with modify permission", expectedBaseUri.resolve("abc/password").toString(), userDto.getLinks().getLinkBy("password").get().getHref());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetPasswordLinkOnlyForDefaultUserType() {
|
||||
User user = createDefaultUser();
|
||||
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||
when(userManager.isTypeDefault(eq(user))).thenReturn(false);
|
||||
|
||||
UserDto userDto = mapper.map(user);
|
||||
|
||||
assertFalse("expected no password link", userDto.getLinks().getLinkBy("password").isPresent());
|
||||
assertEquals("expected convert to external link with modify permission", expectedBaseUri.resolve("abc/convert-to-external").toString(), userDto.getLinks().getLinkBy("convertToExternal").get().getHref());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -148,12 +148,6 @@ class DefaultRepositoryRoleManagerTest {
|
||||
verify(dao).modify(role);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotModifyRole_whenTypeChanged() {
|
||||
assertThrows(ScmConstraintViolationException.class, () -> manager.modify(new RepositoryRole(CUSTOM_ROLE_NAME, singletonList("changed"), null)));
|
||||
verify(dao, never()).modify(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotModifyRole_whenRoleDoesNotExists() {
|
||||
assertThrows(NotFoundException.class, () -> manager.modify(new RepositoryRole("noSuchRole", singletonList("changed"), null)));
|
||||
|
||||
@@ -102,7 +102,7 @@ class JwtAccessTokenBuilderTest {
|
||||
void testBuild() {
|
||||
JwtAccessToken token = factory.create().subject("dent")
|
||||
.issuer("https://www.scm-manager.org")
|
||||
.expiresIn(5, TimeUnit.SECONDS)
|
||||
.expiresIn(1, TimeUnit.MINUTES)
|
||||
.custom("a", "b")
|
||||
.scope(Scope.valueOf("repo:*"))
|
||||
.build();
|
||||
|
||||
@@ -21,66 +21,52 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.user;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
package sonia.scm.user;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.store.JAXBConfigurationStoreFactory;
|
||||
import sonia.scm.user.xml.XmlUserDAO;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.Rule;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@SubjectAware(
|
||||
username = "trillian",
|
||||
password = "secret",
|
||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||
username = "trillian",
|
||||
password = "secret",
|
||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||
)
|
||||
public class DefaultUserManagerTest extends UserManagerTestBase
|
||||
{
|
||||
public class DefaultUserManagerTest extends UserManagerTestBase {
|
||||
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
|
||||
private UserDAO userDAO ;
|
||||
private User trillian;
|
||||
private UserDAO userDAO;
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public UserManager createManager()
|
||||
{
|
||||
public UserManager createManager() {
|
||||
return new DefaultUserManager(createXmlUserDAO());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void initDao() {
|
||||
trillian = UserTestData.createTrillian();
|
||||
User trillian = UserTestData.createTrillian();
|
||||
trillian.setPassword("oldEncrypted");
|
||||
|
||||
userDAO = mock(UserDAO.class);
|
||||
@@ -108,15 +94,6 @@ public class DefaultUserManagerTest extends UserManagerTestBase
|
||||
Assertions.assertThat(userCaptor.getValue().getPassword()).isEqualTo("newEncrypted");
|
||||
}
|
||||
|
||||
@Test(expected = ChangePasswordNotAllowedException.class)
|
||||
public void shouldFailOverwritePasswordForWrongType() {
|
||||
trillian.setType("wrongType");
|
||||
|
||||
UserManager userManager = new DefaultUserManager(userDAO);
|
||||
|
||||
userManager.overwritePassword("trillian", "---");
|
||||
}
|
||||
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void shouldFailOverwritePasswordForMissingUser() {
|
||||
UserManager userManager = new DefaultUserManager(userDAO);
|
||||
@@ -124,6 +101,16 @@ public class DefaultUserManagerTest extends UserManagerTestBase
|
||||
userManager.overwritePassword("notExisting", "---");
|
||||
}
|
||||
|
||||
@Test(expected = ChangePasswordNotAllowedException.class)
|
||||
public void shouldFailOverwritePasswordForExternalUser() {
|
||||
User trillian = new User("trillian");
|
||||
trillian.setExternal(true);
|
||||
when(userDAO.get("trillian")).thenReturn(trillian);
|
||||
UserManager userManager = new DefaultUserManager(userDAO);
|
||||
|
||||
userManager.overwritePassword("trillian", "---");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSucceedOverwritePassword() {
|
||||
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.user;
|
||||
|
||||
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.config.ScmConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class InternalToExternalUserConverterTest {
|
||||
|
||||
@Mock
|
||||
ScmConfiguration scmConfiguration;
|
||||
|
||||
@InjectMocks
|
||||
InternalToExternalUserConverter converter;
|
||||
|
||||
@Test
|
||||
void shouldNotConvertExternalUser() {
|
||||
User external = new User();
|
||||
external.setExternal(true);
|
||||
|
||||
User user = converter.convert(external);
|
||||
|
||||
assertThat(user).isSameAs(external);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotConvertIfConfigDisabled() {
|
||||
when(scmConfiguration.isEnabledUserConverter()).thenReturn(false);
|
||||
User external = new User();
|
||||
external.setExternal(false);
|
||||
|
||||
User user = converter.convert(external);
|
||||
|
||||
assertThat(user).isSameAs(external);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnConvertedUser() {
|
||||
when(scmConfiguration.isEnabledUserConverter()).thenReturn(true);
|
||||
User internal = new User();
|
||||
internal.setExternal(false);
|
||||
|
||||
User external = converter.convert(internal);
|
||||
|
||||
assertThat(external).isInstanceOf(User.class);
|
||||
assertThat(external.isExternal()).isTrue();
|
||||
assertThat(external.getPassword()).isNull();
|
||||
}
|
||||
}
|
||||
@@ -91,15 +91,12 @@ class HttpProtocolServletTest {
|
||||
@BeforeEach
|
||||
void prepareMocks() {
|
||||
when(userAgentParser.parse(request)).thenReturn(userAgent);
|
||||
when(userAgent.isBrowser()).thenReturn(true);
|
||||
when(userAgent.isScmClient()).thenReturn(false);
|
||||
when(request.getRequestURI()).thenReturn("uri");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDispatchBrowserRequests() throws ServletException, IOException {
|
||||
when(userAgent.isBrowser()).thenReturn(true);
|
||||
when(request.getRequestURI()).thenReturn("uri");
|
||||
|
||||
servlet.service(request, response);
|
||||
|
||||
verify(dispatcher).dispatch(request, response, "uri");
|
||||
@@ -113,7 +110,7 @@ class HttpProtocolServletTest {
|
||||
@BeforeEach
|
||||
void prepareMocks() {
|
||||
when(userAgentParser.parse(request)).thenReturn(userAgent);
|
||||
when(userAgent.isBrowser()).thenReturn(false);
|
||||
when(userAgent.isScmClient()).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user