Mind review findings

This commit is contained in:
Eduard Heimbuch
2020-10-21 12:46:26 +02:00
parent 9f29c13ae0
commit 69950f63b3
10 changed files with 83 additions and 69 deletions

View File

@@ -36,7 +36,7 @@ Example: If anonymous access is enabled and the "_anonymous" user has full acces
The url of the RSS Release Feed for SCM-Manager. This provides up-to-date version information. To disable this feature just leave the url blank.
#### User converter
Internal users will automatically be converted to external on their first login using an external system. After convertion the users may only log in using the external system.
Internal users will automatically be converted to external on their first login using an external system. After conversion the users may only log in using the external system.
#### Fallback Mail Domain Name
This domain name will be used to create email addresses for users without one when needed. It will not be used to send mails nor will be accessed otherwise.

View File

@@ -19,6 +19,6 @@ The "Create User" form can be used to create new users in SCM-Manager. New users
### User Details Page
The user details page shows the information about the user.
The active box shows whether the user is able to use SCM-Manager. The external box shows if it is an internal user or is managed by an external system.
The active box shows whether the user is able to use SCM-Manager. The external box shows if it is an internal user or whether it is managed by an external system.
![User-Information](assets/user-information.png)

View File

@@ -5,7 +5,7 @@ subtitle: Settings
### General
In the general settings the display name, e-mail address, external flag and active status of an account can be edited.
If an user is converted from internal to external the passwords will be removed. Switching an external user to internal user a password must be set using the password modal dialogue.
If a user is converted from internal to external the password is going to be removed. When switching an external user to an internal one, a password must be set using the password modal dialogue.
![User Password Modal](assets/user-password-modal.png)

View File

@@ -80,6 +80,7 @@ public final class SyncingRealmHelper {
* @param ctx administration context
* @param userManager user manager
* @param groupManager group manager
* @deprecated
*/
@Deprecated
public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager) {
@@ -110,17 +111,9 @@ public final class SyncingRealmHelper {
public void store(final Group group) {
ctx.runAsAdmin(() -> {
if (groupManager.get(group.getId()) != null) {
try {
groupManager.modify(group);
} catch (NotFoundException e) {
throw new IllegalStateException("got NotFoundException though group " + group.getName() + " could be loaded", e);
}
modifyGroup(group);
} else {
try {
groupManager.create(group);
} catch (AlreadyExistsException e) {
throw new IllegalStateException("got AlreadyExistsException though group " + group.getName() + " could not be loaded", e);
}
createNewGroup(group);
}
});
}
@@ -133,6 +126,26 @@ public final class SyncingRealmHelper {
public void store(final User user) {
ctx.runAsAdmin(() -> {
if (userManager.contains(user.getName())) {
modifyUser(user);
} else {
createNewUser(user);
}
});
}
private void createNewUser(User user) {
try {
User clone = user.clone();
// New user created by syncing realm helper is always external
clone.setExternal(true);
userManager.create(clone);
} catch (AlreadyExistsException e) {
throw new IllegalStateException("got AlreadyExistsException though user " + user.getName() + " could not be loaded", e);
}
}
private void modifyUser(User user) {
User clone = user.clone();
if (!externalUserConverters.isEmpty()) {
log.debug("execute available user converters");
@@ -146,17 +159,21 @@ public final class SyncingRealmHelper {
} catch (NotFoundException e) {
throw new IllegalStateException("got NotFoundException though user " + clone.getName() + " could be loaded", e);
}
} else {
try {
User clone = user.clone();
// New user created by syncing realm helper is always external
clone.setExternal(true);
userManager.create(clone);
} catch (AlreadyExistsException e) {
throw new IllegalStateException("got AlreadyExistsException though user " + user.getName() + " could not be loaded", e);
}
private void createNewGroup(Group group) {
try {
groupManager.create(group);
} catch (AlreadyExistsException e) {
throw new IllegalStateException("got AlreadyExistsException though group " + group.getName() + " could not be loaded", e);
}
}
});
private void modifyGroup(Group group) {
try {
groupManager.modify(group);
} catch (NotFoundException e) {
throw new IllegalStateException("got NotFoundException though group " + group.getName() + " could be loaded", e);
}
}
}

View File

@@ -69,9 +69,9 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
private String password;
@Deprecated
/**
* @deprecated Use external instead.
* The user type is replaced by {@link external} flag
* Since 2.8.0
* @since 2.8.0
* @deprecated Use {@link external} instead.
*/
private String type;

View File

@@ -128,10 +128,10 @@ class UserForm extends React.Component<Props, State> {
const { user, passwordValid } = this.state;
event.preventDefault();
if (!this.isInvalid()) {
this.props.submitForm(this.state.user);
if (user.password && passwordValid && user._links?.password) {
setPassword((user._links.password as Link).href, user.password).catch(error => this.setState({ error }));
}
this.props.submitForm(this.state.user);
}
};
@@ -208,7 +208,7 @@ class UserForm extends React.Component<Props, State> {
<Checkbox
label={t("user.externalFlag")}
onChange={this.handleExternalFlagChange}
checked={!!user?.external && user.external}
checked={!!user?.external}
helpText={t("help.externalFlagHelpText")}
/>
</div>

View File

@@ -42,7 +42,8 @@ class ChangePasswordNavLink extends React.Component<Props> {
}
hasPermissionToSetPassword = () => {
return this.props.user._links.password;
const { user } = this.props;
return !user.external && user._links.password;
};
}

View File

@@ -64,7 +64,7 @@ class Details extends React.Component<Props> {
<tr>
<th>{t("user.externalFlag")}</th>
<td>
<Checkbox checked={!!user?.external && user.external} />
<Checkbox checked={!!user.external} />
</td>
</tr>
<tr>

View File

@@ -389,8 +389,8 @@ public class DefaultUserManager extends AbstractUserManager
if (user == null) {
throw new NotFoundException(User.class, userId);
}
if (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);

View File

@@ -24,31 +24,22 @@
package sonia.scm.user;
//~--- non-JDK imports --------------------------------------------------------
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(
@@ -56,31 +47,26 @@ import org.junit.Rule;
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);
@@ -115,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);