import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
-import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import java.net.HttpURLConnection;
import java.security.SecureRandom;
import org.sonar.server.user.index.UserIndexer;
import org.sonar.server.util.Validation;
+import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Lists.newArrayList;
-import static java.util.Arrays.asList;
-import static org.sonar.api.CoreProperties.CORE_AUTHENTICATOR_LOCAL_USERS;
@ServerSide
public class UserUpdater {
private final DbClient dbClient;
private final UserIndexer userIndexer;
private final System2 system2;
- private final SecurityRealmFactory realmFactory;
- private final List<String> technicalUsers;
- public UserUpdater(NewUserNotifier newUserNotifier, Settings settings, DbClient dbClient, UserIndexer userIndexer, System2 system2, SecurityRealmFactory realmFactory) {
+ public UserUpdater(NewUserNotifier newUserNotifier, Settings settings, DbClient dbClient, UserIndexer userIndexer, System2 system2) {
this.newUserNotifier = newUserNotifier;
this.settings = settings;
this.dbClient = dbClient;
this.userIndexer = userIndexer;
this.system2 = system2;
- this.realmFactory = realmFactory;
- this.technicalUsers = asList(settings.getStringArray(CORE_AUTHENTICATOR_LOCAL_USERS));
}
/**
if (newUser.password() != null) {
updateUser.setPassword(newUser.password());
}
+ // Hack to allow to change the password of the user
+ existingUser.setLocal(true);
updateUserDto(dbSession, updateUser, existingUser);
updateUser(dbSession, existingUser);
addDefaultGroup(dbSession, existingUser);
userDto.setEmail(email);
}
- String password = updateUser.password();
- if (updateUser.isPasswordChanged()) {
- validatePasswords(password, messages);
- checkPasswordChangeAllowed(updateUser.login(), messages);
- if (Strings.isNullOrEmpty(password)) {
- userDto.setSalt(null);
- userDto.setCryptedPassword(null);
- } else {
+ if (isNewExternalIdentityNotEqualsToSonaQube(updateUser)) {
+ setExternalIdentity(userDto, updateUser.externalIdentity());
+ userDto.setSalt(null);
+ userDto.setCryptedPassword(null);
+ } else {
+ String password = updateUser.password();
+ if (updateUser.isPasswordChanged()) {
+ validatePasswords(password, messages);
+ checkPasswordChangeAllowed(userDto, messages);
setEncryptedPassWord(password, userDto);
}
}
}
}
- setExternalIdentity(userDto, updateUser.externalIdentity());
+ if (updateUser.isExternalIdentityChanged()) {
+ setExternalIdentity(userDto, updateUser.externalIdentity());
+ }
if (!messages.isEmpty()) {
throw new BadRequestException(messages);
if (externalIdentity == null) {
dto.setExternalIdentity(dto.getLogin());
dto.setExternalIdentityProvider(SQ_AUTHORITY);
+ dto.setLocal(true);
} else {
dto.setExternalIdentity(externalIdentity.getId());
dto.setExternalIdentityProvider(externalIdentity.getProvider());
+ dto.setLocal(false);
}
}
private static void checkNotEmptyParam(@Nullable String value, String param, List<Message> messages) {
- if (Strings.isNullOrEmpty(value)) {
+ if (isNullOrEmpty(value)) {
messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, param));
}
}
private static String validateLoginFormat(@Nullable String login, List<Message> messages) {
checkNotEmptyParam(login, LOGIN_PARAM, messages);
- if (!Strings.isNullOrEmpty(login)) {
+ if (!isNullOrEmpty(login)) {
if (login.length() < LOGIN_MIN_LENGTH) {
messages.add(Message.of(Validation.IS_TOO_SHORT_MESSAGE, LOGIN_PARAM, LOGIN_MIN_LENGTH));
} else if (login.length() > LOGIN_MAX_LENGTH) {
}
}
- private void checkPasswordChangeAllowed(String login, List<Message> messages) {
- if (realmFactory.hasExternalAuthentication() && !technicalUsers.contains(login)) {
+ private static void checkPasswordChangeAllowed(UserDto userDto, List<Message> messages) {
+ if (!userDto.isLocal()) {
messages.add(Message.of("user.password_cant_be_changed_on_external_auth"));
}
}
+ private static boolean isNewExternalIdentityNotEqualsToSonaQube(UpdateUser updateUser) {
+ ExternalIdentity externalIdentity = updateUser.externalIdentity();
+ if (updateUser.isExternalIdentityChanged() && externalIdentity != null) {
+ return !externalIdentity.getProvider().equals(SQ_AUTHORITY);
+ }
+ return false;
+ }
+
private static void validatePasswords(@Nullable String password, List<Message> messages) {
- if (password != null && password.length() == 0) {
+ if (password == null || password.length() == 0) {
messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, PASSWORD_PARAM));
}
}
import org.sonar.db.DbTester;
import org.sonar.db.user.GroupDao;
import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.GroupMembershipDao;
import org.sonar.db.user.GroupMembershipQuery;
import org.sonar.db.user.UserDao;
import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserGroupDao;
-import org.sonar.db.user.UserTokenDao;
+import org.sonar.db.user.UserTesting;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.Message;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.sonar.api.CoreProperties.CORE_AUTHENTICATOR_LOCAL_USERS;
import static org.sonar.api.CoreProperties.CORE_DEFAULT_GROUP;
-
+import static org.sonar.db.user.UserTesting.newDisabledUser;
+import static org.sonar.db.user.UserTesting.newUserDto;
public class UserUpdaterTest {
+ static final long NOW = 1418215735482L;
+ static final long PAST = 1000000000000L;
+
static final String DEFAULT_LOGIN = "marius";
- static final String TECHNICAL_USER = "tech_user";
+
@ClassRule
public static EsTester es = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
- @Rule
- public DbTester db = DbTester.create(System2.INSTANCE);
+
System2 system2 = mock(System2.class);
- NewUserNotifier newUserNotifier = mock(NewUserNotifier.class);
+ @Rule
+ public DbTester db = DbTester.create(system2);
+
+ DbClient dbClient = db.getDbClient();
- SecurityRealmFactory realmFactory = mock(SecurityRealmFactory.class);
+ NewUserNotifier newUserNotifier = mock(NewUserNotifier.class);
ArgumentCaptor<NewUserHandler.Context> newUserHandler = ArgumentCaptor.forClass(NewUserHandler.Context.class);
- Settings settings;
- UserDao userDao;
- GroupDao groupDao;
- GroupMembershipFinder groupMembershipFinder;
- DbSession session;
+ Settings settings = new Settings();
+ UserDao userDao = dbClient.userDao();
+ GroupDao groupDao = dbClient.groupDao();
+ GroupMembershipFinder groupMembershipFinder = new GroupMembershipFinder(userDao, dbClient.groupMembershipDao());
+ DbSession session = db.getSession();
UserIndexer userIndexer;
UserUpdater userUpdater;
@Before
public void setUp() {
es.truncateIndices();
- settings = new Settings();
- settings.setProperty(CORE_AUTHENTICATOR_LOCAL_USERS, TECHNICAL_USER);
- session = db.getSession();
- userDao = new UserDao(db.myBatis(), system2);
- groupDao = new GroupDao(system2);
- UserGroupDao userGroupDao = new UserGroupDao();
- GroupMembershipDao groupMembershipDao = new GroupMembershipDao(db.myBatis());
- groupMembershipFinder = new GroupMembershipFinder(userDao, groupMembershipDao);
-
- DbClient dbClient = new DbClient(db.database(), db.myBatis(), userDao, groupDao, userGroupDao, new UserTokenDao());
+
userIndexer = (UserIndexer) new UserIndexer(dbClient, es.client()).setEnabled(true);
userUpdater = new UserUpdater(newUserNotifier, settings, dbClient,
- userIndexer, system2, realmFactory);
+ userIndexer, system2);
+
+ when(system2.now()).thenReturn(NOW);
}
@Test
public void create_user() {
- when(system2.now()).thenReturn(1418215735482L);
createDefaultGroup();
boolean result = userUpdater.create(NewUser.create()
assertThat(dto.getEmail()).isEqualTo("user@mail.com");
assertThat(dto.getScmAccountsAsList()).containsOnly("u1", "u_1", "User 1");
assertThat(dto.isActive()).isTrue();
+ assertThat(dto.isLocal()).isTrue();
assertThat(dto.getSalt()).isNotNull();
assertThat(dto.getCryptedPassword()).isNotNull();
UserDto dto = userDao.selectByLogin(session, "user");
assertThat(dto.getExternalIdentity()).isEqualTo("user");
assertThat(dto.getExternalIdentityProvider()).isEqualTo("sonarqube");
+ assertThat(dto.isLocal()).isTrue();
}
@Test
UserDto dto = userDao.selectByLogin(session, "ABCD");
assertThat(dto.getExternalIdentity()).isEqualTo("user");
assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
+ assertThat(dto.isLocal()).isFalse();
}
@Test
@Test
public void reactivate_user_when_creating_user_with_existing_login() {
- db.prepareDbUnit(getClass(), "reactivate_user.xml");
- when(system2.now()).thenReturn(1418215735486L);
+ addUser(newDisabledUser(DEFAULT_LOGIN)
+ .setLocal(false)
+ .setCreatedAt(PAST)
+ .setUpdatedAt(PAST)
+ );
createDefaultGroup();
boolean result = userUpdater.create(NewUser.create()
assertThat(dto.getName()).isEqualTo("Marius2");
assertThat(dto.getEmail()).isEqualTo("marius2@mail.com");
assertThat(dto.getScmAccounts()).isNull();
+ assertThat(dto.isLocal()).isTrue();
assertThat(dto.getSalt()).isNotEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
assertThat(dto.getCryptedPassword()).isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
- assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L);
- assertThat(dto.getUpdatedAt()).isEqualTo(1418215735486L);
+ assertThat(dto.getCreatedAt()).isEqualTo(PAST);
+ assertThat(dto.getUpdatedAt()).isEqualTo(NOW);
assertThat(result).isTrue();
}
@Test
public void update_external_provider_when_reactivating_user() {
- db.prepareDbUnit(getClass(), "reactivate_user.xml");
- when(system2.now()).thenReturn(1418215735486L);
+ addUser(newDisabledUser(DEFAULT_LOGIN)
+ .setLocal(true)
+ .setCreatedAt(PAST)
+ .setUpdatedAt(PAST)
+ );
createDefaultGroup();
userUpdater.create(NewUser.create()
UserDto dto = userDao.selectByLogin(session, DEFAULT_LOGIN);
assertThat(dto.getExternalIdentity()).isEqualTo("john");
assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
+ assertThat(dto.isLocal()).isFalse();
}
@Test
}
@Test
- public void update_user_external_identity() {
- db.prepareDbUnit(getClass(), "update_user.xml");
- when(system2.now()).thenReturn(1418215735486L);
+ public void update_user_external_identity_when_user_was_not_local() {
+ addUser(UserTesting.newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
+ .setCreatedAt(PAST)
+ .setUpdatedAt(PAST)
+ );
createDefaultGroup();
userUpdater.update(UpdateUser.create(DEFAULT_LOGIN)
.setName("Marius2")
- .setPassword("password2")
+ .setEmail("marius2@email.com")
+ .setPassword(null)
.setExternalIdentity(new ExternalIdentity("github", "john")));
session.commit();
session.clearCache();
UserDto dto = userDao.selectByLogin(session, DEFAULT_LOGIN);
assertThat(dto.getExternalIdentity()).isEqualTo("john");
assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
+ assertThat(dto.getUpdatedAt()).isEqualTo(NOW);
+ }
+
+ @Test
+ public void update_user_external_identity_when_user_was_local() {
+ addUser(UserTesting.newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
+ .setCreatedAt(PAST)
+ .setUpdatedAt(PAST)
+ );
+ createDefaultGroup();
+
+ userUpdater.update(UpdateUser.create(DEFAULT_LOGIN)
+ .setName("Marius2")
+ .setEmail("marius2@email.com")
+ .setPassword(null)
+ .setExternalIdentity(new ExternalIdentity("github", "john")));
+ session.commit();
+ session.clearCache();
+
+ UserDto dto = userDao.selectByLogin(session, DEFAULT_LOGIN);
+ assertThat(dto.getExternalIdentity()).isEqualTo("john");
+ assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
+ // Password must be removed
+ assertThat(dto.getCryptedPassword()).isNull();
+ assertThat(dto.getSalt()).isNull();
+ assertThat(dto.getUpdatedAt()).isEqualTo(NOW);
}
@Test
}
@Test
- public void update_password_with_null_value() {
- db.prepareDbUnit(getClass(), "update_user.xml");
+ public void fail_to_set_null_password_when_local_user() {
+ addUser(UserTesting.newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com"));
createDefaultGroup();
- userUpdater.update(UpdateUser.create(DEFAULT_LOGIN)
- .setPassword(null));
- session.commit();
- session.clearCache();
-
- UserDto dto = userDao.selectByLogin(session, DEFAULT_LOGIN);
- assertThat(dto.getSalt()).isNull();
- assertThat(dto.getCryptedPassword()).isNull();
+ try {
+ userUpdater.update(UpdateUser.create(DEFAULT_LOGIN)
+ .setPassword(null));
+ } catch (BadRequestException e) {
+ assertThat(e.errors().messages()).containsOnly(Message.of("errors.cant_be_empty", "Password"));
+ }
}
@Test
- public void fail_to_update_password_when_external_auth_is_used() {
- db.prepareDbUnit(getClass(), "update_user.xml");
+ public void fail_to_update_password_when_user_is_not_local() {
+ UserDto user = newUserDto()
+ .setLogin(DEFAULT_LOGIN)
+ .setLocal(false);
+ userDao.insert(session, user);
+ session.commit();
createDefaultGroup();
- when(realmFactory.hasExternalAuthentication()).thenReturn(true);
try {
userUpdater.update(UpdateUser.create(DEFAULT_LOGIN)
}
}
- @Test
- public void update_password_of_technical_user_when_external_auth_is_used() {
- db.prepareDbUnit(getClass(), "update_technical_user.xml");
- createDefaultGroup();
- when(realmFactory.hasExternalAuthentication()).thenReturn(true);
-
- userUpdater.update(UpdateUser.create(TECHNICAL_USER)
- .setPassword("password2"));
-
- UserDto dto = userDao.selectByLogin(session, TECHNICAL_USER);
- assertThat(dto.getSalt()).isNotEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
- }
-
@Test
public void not_associate_default_group_when_updating_user() {
db.prepareDbUnit(getClass(), "associate_default_groups_when_updating_user.xml");
groupDao.insert(session, new GroupDto().setName("sonar-users").setDescription("Sonar Users"));
session.commit();
}
+
+ private UserDto addUser(UserDto user) {
+ userDao.insert(session, user);
+ session.commit();
+ return user;
+ }
}
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.user.ExternalIdentity;
import org.sonar.server.user.NewUser;
import org.sonar.server.user.NewUserNotifier;
import org.sonar.server.user.SecurityRealmFactory;
userIndexer = (UserIndexer) new UserIndexer(dbClient, esTester.client()).setEnabled(true);
index = new UserIndex(esTester.client());
- userUpdater = new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2, realmFactory);
+ userUpdater = new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2);
tester = new WsTester(new UsersWs(new ChangePasswordAction(userUpdater, userSessionRule)));
controller = tester.controller("api/users");
}
@Test(expected = BadRequestException.class)
public void fail_to_update_password_on_external_auth() throws Exception {
- createUser();
+ userUpdater.create(NewUser.create()
+ .setEmail("john@email.com")
+ .setLogin("john")
+ .setName("John")
+ .setScmAccounts(newArrayList("jn"))
+ .setExternalIdentity(new ExternalIdentity("gihhub", "john")));
session.clearCache();
when(realmFactory.hasExternalAuthentication()).thenReturn(true);
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.NewUserNotifier;
-import org.sonar.server.user.SecurityRealmFactory;
import org.sonar.server.user.UserUpdater;
import org.sonar.server.user.index.UserDoc;
import org.sonar.server.user.index.UserIndex;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-
public class CreateActionTest {
static final Settings settings = new Settings().setProperty("sonar.defaultGroup", "sonar-users");
userIndexer = (UserIndexer) new UserIndexer(dbClient, esTester.client()).setEnabled(true);
index = new UserIndex(esTester.client());
tester = new WsTester(new UsersWs(new CreateAction(dbClient,
- new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2, mock(SecurityRealmFactory.class)),
+ new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2),
i18n, userSessionRule, new UserJsonWriter(userSessionRule))));
controller = tester.controller("api/users");
}
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.NewUserNotifier;
-import org.sonar.server.user.SecurityRealmFactory;
import org.sonar.server.user.UserUpdater;
import org.sonar.server.user.index.UserDoc;
import org.sonar.server.user.index.UserIndex;
userIndexer = (UserIndexer) new UserIndexer(dbClient, esTester.client()).setEnabled(true);
index = new UserIndex(esTester.client());
ws = new WsTester(new UsersWs(new DeactivateAction(
- new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2, mock(SecurityRealmFactory.class)), userSessionRule,
+ new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2), userSessionRule,
new UserJsonWriter(userSessionRule), dbClient)));
controller = ws.controller("api/users");
}
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.NewUserNotifier;
-import org.sonar.server.user.SecurityRealmFactory;
import org.sonar.server.user.UserUpdater;
import org.sonar.server.user.index.UserIndexDefinition;
import org.sonar.server.user.index.UserIndexer;
userIndexer = (UserIndexer) new UserIndexer(dbClient, esTester.client()).setEnabled(true);
tester = new WsTester(new UsersWs(new UpdateAction(
- new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2, mock(SecurityRealmFactory.class)), userSessionRule,
+ new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2), userSessionRule,
new UserJsonWriter(userSessionRule), dbClient)));
controller = tester.controller("api/users");
}
public static UserDto newUserDto(String login, String name, String email) {
return new UserDto()
.setActive(true)
+ .setLocal(true)
.setName(name)
.setEmail(email)
.setLogin(login)
.setCreatedAt(nextLong())
.setUpdatedAt(nextLong());
}
+
+ public static UserDto newLocalUser(String login, String name, String email) {
+ return new UserDto()
+ .setActive(true)
+ .setLocal(true)
+ .setName(name)
+ .setEmail(email)
+ .setLogin(login)
+ .setScmAccounts(randomAlphanumeric(40))
+ .setExternalIdentity(login)
+ .setExternalIdentityProvider("sonarqube")
+ .setRememberToken(randomAlphanumeric(500))
+ .setRememberTokenExpiresAt(new Date((long)nextInt()))
+ .setSalt(randomAlphanumeric(40))
+ .setCryptedPassword(randomAlphanumeric(40))
+ .setCreatedAt(nextLong())
+ .setUpdatedAt(nextLong());
+ }
+
+ public static UserDto newExternalUser(String login, String name, String email) {
+ return new UserDto()
+ .setActive(true)
+ .setLocal(false)
+ .setName(name)
+ .setEmail(email)
+ .setLogin(login)
+ .setScmAccounts(randomAlphanumeric(40))
+ .setExternalIdentity(randomAlphanumeric(40))
+ .setExternalIdentityProvider(randomAlphanumeric(40))
+ .setCreatedAt(nextLong())
+ .setUpdatedAt(nextLong());
+ }
+
+ public static UserDto newDisabledUser(String login){
+ return new UserDto()
+ .setLogin(login)
+ .setActive(false)
+ .setCreatedAt(nextLong())
+ .setUpdatedAt(nextLong());
+ }
}