From: Julien Lancelot Date: Fri, 11 May 2018 07:20:20 +0000 (+0200) Subject: SONAR-10597 Update USER_TOKENS#LOGIN to USER_UUID X-Git-Tag: 7.5~1163 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=63947f4bf774b4dd224c5697f72e0527d14eb720;p=sonarqube.git SONAR-10597 Update USER_TOKENS#LOGIN to USER_UUID * SONAR-10597 Update USER_TOKENS#LOGIN to USER_UUD in DB * SONAR-10597 Update UserTokenDao to return user uuid * SONAR-10597 Fix api/user_tokens/generate to correctly use user uuid * SONAR-10597 Fix api/user_tokens/revoke to correctly use user uuid * SONAR-10597 Fix authentication by token to correctly use user uuid * SONAR-10597 Fix api/user_tokens/search to correctly use user uuid * SONAR-10597 Fix api/users/search to correctly use user uuid * SONAR-10597 Add ITs to check user tokens after login update --- diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl index 885d377e2c0..3c5de10a498 100644 --- a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -701,13 +701,13 @@ CREATE TABLE "CE_SCANNER_CONTEXT" ( CREATE TABLE "USER_TOKENS" ( "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), - "LOGIN" VARCHAR(255) NOT NULL, + "USER_UUID" VARCHAR(255) NOT NULL, "NAME" VARCHAR(100) NOT NULL, "TOKEN_HASH" VARCHAR(255) NOT NULL, "CREATED_AT" BIGINT NOT NULL ); CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS" ("TOKEN_HASH"); -CREATE UNIQUE INDEX "USER_TOKENS_LOGIN_NAME" ON "USER_TOKENS" ("LOGIN", "NAME"); +CREATE UNIQUE INDEX "USER_TOKENS_USER_UUID_NAME" ON "USER_TOKENS" ("USER_UUID", "NAME"); CREATE TABLE "ES_QUEUE" ( diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenCount.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenCount.java index 6bcbc2254a5..9617816fda8 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenCount.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenCount.java @@ -20,15 +20,15 @@ package org.sonar.db.user; public class UserTokenCount { - private String login; + private String userUuid; private Integer tokenCount; - public String getLogin() { - return login; + public String getUserUuid() { + return userUuid; } - public UserTokenCount setLogin(String login) { - this.login = login; + public UserTokenCount setUserUuid(String userUuid) { + this.userUuid = userUuid; return this; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java index 5a3b8c0054f..8391010fe3a 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java @@ -19,15 +19,15 @@ */ package org.sonar.db.user; -import com.google.common.base.Optional; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.CheckForNull; import org.sonar.db.Dao; import org.sonar.db.DbSession; -import org.sonar.db.RowNotFoundException; -import static java.lang.String.format; +import static org.sonar.core.util.stream.MoreCollectors.toList; import static org.sonar.db.DatabaseUtils.executeLargeInputs; public class UserTokenDao implements Dao { @@ -35,35 +35,28 @@ public class UserTokenDao implements Dao { mapper(dbSession).insert(userTokenDto); } - public UserTokenDto selectOrFailByTokenHash(DbSession dbSession, String tokenHash) { - UserTokenDto userToken = mapper(dbSession).selectByTokenHash(tokenHash); - if (userToken == null) { - throw new RowNotFoundException(format("User token with token hash '%s' not found", tokenHash)); - } - - return userToken; - } - - public Optional selectByTokenHash(DbSession dbSession, String tokenHash) { - return Optional.fromNullable(mapper(dbSession).selectByTokenHash(tokenHash)); + @CheckForNull + public UserTokenDto selectByTokenHash(DbSession dbSession, String tokenHash) { + return mapper(dbSession).selectByTokenHash(tokenHash); } - public Optional selectByLoginAndName(DbSession dbSession, String login, String name) { - return Optional.fromNullable(mapper(dbSession).selectByLoginAndName(login, name)); + @CheckForNull + public UserTokenDto selectByUserAndName(DbSession dbSession, UserDto user, String name) { + return mapper(dbSession).selectByUserUuidAndName(user.getUuid(), name); } - public List selectByLogin(DbSession dbSession, String login) { - return mapper(dbSession).selectByLogin(login); + public List selectByUser(DbSession dbSession, UserDto user) { + return mapper(dbSession).selectByUserUuid(user.getUuid()); } - public Map countTokensByLogins(DbSession dbSession, List logins) { - Map result = new HashMap<>(logins.size()); + public Map countTokensByUsers(DbSession dbSession, Collection users) { + Map result = new HashMap<>(users.size()); executeLargeInputs( - logins, + users.stream().map(UserDto::getUuid).collect(toList()), input -> { - List userTokenCounts = mapper(dbSession).countTokensByLogins(input); + List userTokenCounts = mapper(dbSession).countTokensByUserUuids(input); for (UserTokenCount userTokenCount : userTokenCounts) { - result.put(userTokenCount.getLogin(), userTokenCount.tokenCount()); + result.put(userTokenCount.getUserUuid(), userTokenCount.tokenCount()); } return userTokenCounts; }); @@ -71,12 +64,12 @@ public class UserTokenDao implements Dao { return result; } - public void deleteByLogin(DbSession dbSession, String login) { - mapper(dbSession).deleteByLogin(login); + public void deleteByUser(DbSession dbSession, UserDto user) { + mapper(dbSession).deleteByUserUuid(user.getUuid()); } - public void deleteByLoginAndName(DbSession dbSession, String login, String name) { - mapper(dbSession).deleteByLoginAndName(login, name); + public void deleteByUserAndName(DbSession dbSession, UserDto user, String name) { + mapper(dbSession).deleteByUserUuidAndName(user.getUuid(), name); } private static UserTokenMapper mapper(DbSession dbSession) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java index d700c5e1cfc..c014283f2b1 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java @@ -22,17 +22,17 @@ package org.sonar.db.user; import static org.sonar.db.user.UserTokenValidator.checkTokenHash; public class UserTokenDto { - private String login; + private String userUuid; private String name; private String tokenHash; private Long createdAt; - public String getLogin() { - return login; + public String getUserUuid() { + return userUuid; } - public UserTokenDto setLogin(String login) { - this.login = login; + public UserTokenDto setUserUuid(String userUuid) { + this.userUuid = userUuid; return this; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenMapper.java index c06e4738cb9..82a0ac7d7d6 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenMapper.java @@ -27,13 +27,13 @@ public interface UserTokenMapper { UserTokenDto selectByTokenHash(String tokenHash); - UserTokenDto selectByLoginAndName(@Param("login") String login, @Param("name") String name); + UserTokenDto selectByUserUuidAndName(@Param("userUuid") String userUuid, @Param("name") String name); - List selectByLogin(String login); + List selectByUserUuid(String userUuid); - void deleteByLogin(String login); + void deleteByUserUuid(String userUuid); - void deleteByLoginAndName(@Param("login") String login, @Param("name") String name); + void deleteByUserUuidAndName(@Param("userUuid") String userUuid, @Param("name") String name); - List countTokensByLogins(@Param("logins") List logins); + List countTokensByUserUuids(@Param("userUuids") List userUuids); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml index 5ffa6465199..085e5cbc448 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml @@ -4,7 +4,7 @@ - t.login as "login", + t.user_uuid as "userUuid", t.name as "name", t.token_hash as "tokenHash", t.created_at as "createdAt" @@ -12,12 +12,12 @@ insert into user_tokens ( - login, + user_uuid, name, token_hash, created_at ) values ( - #{login,jdbcType=VARCHAR}, + #{userUuid,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{tokenHash,jdbcType=VARCHAR}, #{createdAt,jdbcType=BIGINT} @@ -31,36 +31,36 @@ WHERE t.token_hash=#{tokenHash} - SELECT FROM user_tokens t - WHERE t.login=#{login} and t.name=#{name} + WHERE t.user_uuid=#{userUuid} and t.name=#{name} - SELECT FROM user_tokens t - WHERE t.login=#{login} + WHERE t.user_uuid=#{userUuid} - + SELECT t.user_uuid as "userUuid", count(t.name) as "tokenCount" FROM user_tokens t - WHERE t.login in - - #{login} + WHERE t.user_uuid in + + #{userUuid} - GROUP BY t.login + GROUP BY t.user_uuid - - DELETE FROM user_tokens WHERE login=#{login} + + DELETE FROM user_tokens WHERE user_uuid=#{userUuid} - - DELETE FROM user_tokens WHERE login=#{login} and name=#{name} + + DELETE FROM user_tokens WHERE user_uuid=#{userUuid} and name=#{name} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java index 69343f0b9cc..1a9ecabfd31 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java @@ -42,6 +42,7 @@ import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.db.user.GroupTesting.newGroupDto; import static org.sonar.db.user.UserTesting.newDisabledUser; import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.db.user.UserTokenTesting.newUserToken; public class UserDbTester { private final DbTester db; @@ -227,7 +228,7 @@ public class UserDbTester { checkArgument(!project.isPrivate(), "No permission to group AnyOne can be granted on a private project"); checkArgument(!ProjectPermissions.PUBLIC_PERMISSIONS.contains(permission), "permission %s can't be granted on a public project", permission); - checkArgument(project.getMainBranchProjectUuid()==null, "Permissions can't be granted on branches"); + checkArgument(project.getMainBranchProjectUuid() == null, "Permissions can't be granted on branches"); GroupPermissionDto dto = new GroupPermissionDto() .setOrganizationUuid(project.getOrganizationUuid()) .setGroupId(null) @@ -247,7 +248,7 @@ public class UserDbTester { checkArgument(group.getOrganizationUuid().equals(project.getOrganizationUuid()), "Different organizations"); checkArgument(project.isPrivate() || !ProjectPermissions.PUBLIC_PERMISSIONS.contains(permission), "%s can't be granted on a public project", permission); - checkArgument(project.getMainBranchProjectUuid()==null, "Permissions can't be granted on branches"); + checkArgument(project.getMainBranchProjectUuid() == null, "Permissions can't be granted on branches"); GroupPermissionDto dto = new GroupPermissionDto() .setOrganizationUuid(group.getOrganizationUuid()) .setGroupId(group.getId()) @@ -320,7 +321,7 @@ public class UserDbTester { public UserPermissionDto insertProjectPermissionOnUser(UserDto user, String permission, ComponentDto project) { checkArgument(project.isPrivate() || !ProjectPermissions.PUBLIC_PERMISSIONS.contains(permission), "%s can't be granted on a public project", permission); - checkArgument(project.getMainBranchProjectUuid()==null, "Permissions can't be granted on branches"); + checkArgument(project.getMainBranchProjectUuid() == null, "Permissions can't be granted on branches"); UserPermissionDto dto = new UserPermissionDto(project.getOrganizationUuid(), permission, user.getId(), project.getId()); db.getDbClient().userPermissionDao().insert(db.getSession(), dto); db.commit(); @@ -342,4 +343,14 @@ public class UserDbTester { .map(OrganizationPermission::fromKey) .collect(MoreCollectors.toList()); } + + @SafeVarargs + public final UserTokenDto insertToken(UserDto user, Consumer... populators) { + UserTokenDto dto = newUserToken().setUserUuid(user.getUuid()); + stream(populators).forEach(p -> p.accept(dto)); + db.getDbClient().userTokenDao().insert(db.getSession(), dto); + db.commit(); + return dto; + } + } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java index d6d9a9a6c1f..6807d847e9e 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java @@ -19,129 +19,106 @@ */ package org.sonar.db.user; -import com.google.common.base.Optional; import java.util.Map; -import org.assertj.guava.api.Assertions; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.System2; import org.sonar.db.DbSession; import org.sonar.db.DbTester; -import org.sonar.db.RowNotFoundException; -import static com.google.common.collect.Lists.newArrayList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.db.user.UserTokenTesting.newUserToken; - public class UserTokenDaoTest { @Rule public DbTester db = DbTester.create(System2.INSTANCE); @Rule public ExpectedException expectedException = ExpectedException.none(); - DbSession dbSession; - - UserTokenDao underTest; + private DbSession dbSession = db.getSession(); - @Before - public void setUp() { - underTest = db.getDbClient().userTokenDao(); - dbSession = db.getSession(); - } + private UserTokenDao underTest = db.getDbClient().userTokenDao(); @Test public void insert_token() { UserTokenDto userToken = newUserToken(); - insertToken(userToken); + underTest.insert(db.getSession(), userToken); - UserTokenDto userTokenFromDb = underTest.selectOrFailByTokenHash(dbSession, userToken.getTokenHash()); + UserTokenDto userTokenFromDb = underTest.selectByTokenHash(db.getSession(), userToken.getTokenHash()); assertThat(userTokenFromDb).isNotNull(); assertThat(userTokenFromDb.getName()).isEqualTo(userToken.getName()); assertThat(userTokenFromDb.getCreatedAt()).isEqualTo(userToken.getCreatedAt()); assertThat(userTokenFromDb.getTokenHash()).isEqualTo(userToken.getTokenHash()); - assertThat(userTokenFromDb.getLogin()).isEqualTo(userToken.getLogin()); + assertThat(userTokenFromDb.getUserUuid()).isEqualTo(userToken.getUserUuid()); } @Test public void select_by_token_hash() { + UserDto user = db.users().insertUser(); String tokenHash = "123456789"; - insertToken(newUserToken().setTokenHash(tokenHash)); + db.users().insertToken(user, t -> t.setTokenHash(tokenHash)); - Optional result = underTest.selectByTokenHash(dbSession, tokenHash); + UserTokenDto result = underTest.selectByTokenHash(db.getSession(), tokenHash); - Assertions.assertThat(result).isPresent(); + assertThat(result).isNotNull(); } @Test - public void fail_if_token_is_not_found() { - expectedException.expect(RowNotFoundException.class); - expectedException.expectMessage("User token with token hash 'unknown-token-hash' not found"); + public void select_by_user_and_name() { + UserDto user = db.users().insertUser(); + UserTokenDto userToken = db.users().insertToken(user, t -> t.setName("name").setTokenHash("token")); - underTest.selectOrFailByTokenHash(dbSession, "unknown-token-hash"); - } - - @Test - public void select_by_login_and_name() { - UserTokenDto userToken = newUserToken().setLogin("login").setName("name").setTokenHash("token"); - insertToken(userToken); - - Optional optionalResultByLoginAndName = underTest.selectByLoginAndName(dbSession, userToken.getLogin(), userToken.getName()); - UserTokenDto resultByLoginAndName = optionalResultByLoginAndName.get(); - Optional unfoundResult1 = underTest.selectByLoginAndName(dbSession, "unknown-login", userToken.getName()); - Optional unfoundResult2 = underTest.selectByLoginAndName(dbSession, userToken.getLogin(), "unknown-name"); - - Assertions.assertThat(unfoundResult1).isAbsent(); - Assertions.assertThat(unfoundResult2).isAbsent(); - assertThat(resultByLoginAndName.getLogin()).isEqualTo(userToken.getLogin()); + UserTokenDto resultByLoginAndName = underTest.selectByUserAndName(db.getSession(), user, userToken.getName()); + assertThat(resultByLoginAndName.getUserUuid()).isEqualTo(user.getUuid()); assertThat(resultByLoginAndName.getName()).isEqualTo(userToken.getName()); assertThat(resultByLoginAndName.getCreatedAt()).isEqualTo(userToken.getCreatedAt()); assertThat(resultByLoginAndName.getTokenHash()).isEqualTo(userToken.getTokenHash()); + + assertThat(underTest.selectByUserAndName(db.getSession(), user, "unknown-name")).isNull(); } @Test - public void delete_tokens_by_login() { - insertToken(newUserToken().setLogin("login-to-delete")); - insertToken(newUserToken().setLogin("login-to-delete")); - insertToken(newUserToken().setLogin("login-to-keep")); - - underTest.deleteByLogin(dbSession, "login-to-delete"); + public void delete_tokens_by_user() { + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + db.users().insertToken(user1); + db.users().insertToken(user1); + db.users().insertToken(user2); + + underTest.deleteByUser(dbSession, user1); db.commit(); - assertThat(underTest.selectByLogin(dbSession, "login-to-delete")).isEmpty(); - assertThat(underTest.selectByLogin(dbSession, "login-to-keep")).hasSize(1); + assertThat(underTest.selectByUser(dbSession, user1)).isEmpty(); + assertThat(underTest.selectByUser(dbSession, user2)).hasSize(1); } @Test - public void delete_token_by_login_and_name() { - insertToken(newUserToken().setLogin("login").setName("name")); - insertToken(newUserToken().setLogin("login").setName("another-name")); - insertToken(newUserToken().setLogin("another-login").setName("name")); - - underTest.deleteByLoginAndName(dbSession, "login", "name"); - db.commit(); - - Assertions.assertThat(underTest.selectByLoginAndName(dbSession, "login", "name")).isAbsent(); - Assertions.assertThat(underTest.selectByLoginAndName(dbSession, "login", "another-name")).isPresent(); - Assertions.assertThat(underTest.selectByLoginAndName(dbSession, "another-login", "name")).isPresent(); + public void delete_token_by_user_and_name() { + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + db.users().insertToken(user1, t -> t.setName("name")); + db.users().insertToken(user1, t -> t.setName("another-name")); + db.users().insertToken(user2, t -> t.setName("name")); + + underTest.deleteByUserAndName(dbSession, user1, "name"); + + assertThat(underTest.selectByUserAndName(dbSession, user1, "name")).isNull(); + assertThat(underTest.selectByUserAndName(dbSession, user1, "another-name")).isNotNull(); + assertThat(underTest.selectByUserAndName(dbSession, user2, "name")).isNotNull(); } @Test - public void count_tokens_by_login() { - insertToken(newUserToken().setLogin("login").setName("name")); - insertToken(newUserToken().setLogin("login").setName("another-name")); - - Map result = underTest.countTokensByLogins(dbSession, newArrayList("login")); + public void count_tokens_by_user() { + UserDto user = db.users().insertUser(); + db.users().insertToken(user, t -> t.setName("name")); + db.users().insertToken(user, t -> t.setName("another-name")); - assertThat(result.get("login")).isEqualTo(2); - assertThat(result.get("unknown-login")).isNull(); - } + Map result = underTest.countTokensByUsers(dbSession, singletonList(user)); - private void insertToken(UserTokenDto userToken) { - underTest.insert(dbSession, userToken); - dbSession.commit(); + assertThat(result.get(user.getUuid())).isEqualTo(2); + assertThat(result.get("unknown-user_uuid")).isNull(); } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenTesting.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenTesting.java index b3983312546..3a2a5916add 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenTesting.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenTesting.java @@ -25,9 +25,9 @@ import static org.apache.commons.lang.math.RandomUtils.nextLong; public class UserTokenTesting { public static UserTokenDto newUserToken() { return new UserTokenDto() - .setLogin(randomAlphanumeric(255)) - .setName(randomAlphanumeric(100)) - .setTokenHash(randomAlphanumeric(40)) + .setUserUuid("userUuid_" + randomAlphanumeric(40)) + .setName("name_" + randomAlphanumeric(20)) + .setTokenHash("hash_" + randomAlphanumeric(30)) .setCreatedAt(nextLong()); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java index 4a5d0e223e3..c8cf8a2fbb1 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java @@ -46,6 +46,7 @@ public class DbVersion72 implements DbVersion { .add(2116, "Populate ORGANIZATION_UUID in table users", PopulateOrganizationUuidOnUsers.class) .add(2117, "Drop USER_ID from table organizations", DropUserIdFromOrganizations.class) .add(2118, "Rename USER_LOGIN TO USER_UUID on table QPROFILE_CHANGES", RenameUserLoginToUserUuidOnTableQProfileChanges.class) + .add(2119, "Rename LOGIN TO USER_UUID on table USER_TOKENS", RenameLoginToUserUuidOnTableUserTokens.class) ; } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/RenameLoginToUserUuidOnTableUserTokens.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/RenameLoginToUserUuidOnTableUserTokens.java new file mode 100644 index 00000000000..f68c8d32ad9 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/RenameLoginToUserUuidOnTableUserTokens.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.db.migration.version.v72; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.sql.DropIndexBuilder; +import org.sonar.server.platform.db.migration.sql.RenameColumnsBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class RenameLoginToUserUuidOnTableUserTokens extends DdlChange { + + private static final String USER_TOKENS_TABLE = "user_tokens"; + + public RenameLoginToUserUuidOnTableUserTokens(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new DropIndexBuilder(getDialect()) + .setTable(USER_TOKENS_TABLE) + .setName("user_tokens_login_name") + .build()); + + VarcharColumnDef userUuidColumn = newVarcharColumnDefBuilder() + .setColumnName("user_uuid") + .setLimit(255) + .setIsNullable(false) + .build(); + context.execute(new RenameColumnsBuilder(getDialect(), USER_TOKENS_TABLE) + .renameColumn("login", + userUuidColumn) + .build()); + + context.execute(new CreateIndexBuilder(getDialect()) + .setTable(USER_TOKENS_TABLE) + .setName("user_tokens_user_uuid_name") + .setUnique(true) + .addColumn(userUuidColumn) + .addColumn(newVarcharColumnDefBuilder() + .setColumnName("name") + .setLimit(100) + .setIsNullable(false) + .build()) + .build()); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java index 346d97cbcb6..4cd2886c568 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java @@ -34,7 +34,7 @@ public class DbVersion72Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 19); + verifyMigrationCount(underTest, 20); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/RenameLoginToUserUuidOnTableUserTokensTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/RenameLoginToUserUuidOnTableUserTokensTest.java new file mode 100644 index 00000000000..656863f6970 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/RenameLoginToUserUuidOnTableUserTokensTest.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.platform.db.migration.version.v72; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.CoreDbTester; + +import static java.sql.Types.VARCHAR; + +public class RenameLoginToUserUuidOnTableUserTokensTest { + + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(RenameLoginToUserUuidOnTableUserTokensTest.class, "user_tokens.sql"); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private RenameLoginToUserUuidOnTableUserTokens underTest = new RenameLoginToUserUuidOnTableUserTokens(db.database()); + + @Test + public void rename_column() throws SQLException { + underTest.execute(); + + db.assertColumnDefinition("user_tokens", "user_uuid", VARCHAR, 255, false); + db.assertColumnDoesNotExist("user_tokens", "login"); + db.assertUniqueIndex("user_tokens", "user_tokens_user_uuid_name", "user_uuid", "name"); + db.assertIndexDoesNotExist("user_tokens", "user_tokens_login_name"); + } + + public void migration_is_not_reentrant() throws SQLException { + underTest.execute(); + + expectedException.expect(IllegalStateException.class); + + underTest.execute(); + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/RenameLoginToUserUuidOnTableUserTokensTest/user_tokens.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/RenameLoginToUserUuidOnTableUserTokensTest/user_tokens.sql new file mode 100644 index 00000000000..acb1b33f18a --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/RenameLoginToUserUuidOnTableUserTokensTest/user_tokens.sql @@ -0,0 +1,9 @@ +CREATE TABLE "USER_TOKENS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "LOGIN" VARCHAR(255) NOT NULL, + "NAME" VARCHAR(100) NOT NULL, + "TOKEN_HASH" VARCHAR(255) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS" ("TOKEN_HASH"); +CREATE UNIQUE INDEX "USER_TOKENS_LOGIN_NAME" ON "USER_TOKENS" ("LOGIN", "NAME"); \ No newline at end of file diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthenticator.java index a8551096123..d8d8f8b082b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthenticator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthenticator.java @@ -106,16 +106,16 @@ public class BasicAuthenticator { } private UserDto authenticateFromUserToken(String token) { - Optional authenticatedLogin = userTokenAuthenticator.authenticate(token); - if (!authenticatedLogin.isPresent()) { + Optional authenticatedUserUuid = userTokenAuthenticator.authenticate(token); + if (!authenticatedUserUuid.isPresent()) { throw AuthenticationException.newBuilder() .setSource(Source.local(Method.BASIC_TOKEN)) .setMessage("Token doesn't exist") .build(); } try (DbSession dbSession = dbClient.openSession(false)) { - UserDto userDto = dbClient.userDao().selectActiveUserByLogin(dbSession, authenticatedLogin.get()); - if (userDto == null) { + UserDto userDto = dbClient.userDao().selectByUuid(dbSession, authenticatedUserUuid.get()); + if (userDto == null || !userDto.isActive()) { throw AuthenticationException.newBuilder() .setSource(Source.local(Method.BASIC_TOKEN)) .setMessage("User doesn't exist") diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java index fc8f3956ebe..e177bc1f86c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java @@ -94,7 +94,7 @@ public class DeactivateAction implements UsersWsAction { ensureNotLastAdministrator(dbSession, user); Integer userId = user.getId(); - dbClient.userTokenDao().deleteByLogin(dbSession, login); + dbClient.userTokenDao().deleteByUser(dbSession, user); dbClient.propertiesDao().deleteByKeyAndValue(dbSession, DEFAULT_ISSUE_ASSIGNEE, user.getLogin()); dbClient.propertiesDao().deleteByQuery(dbSession, PropertyQuery.builder().setUserId(userId).build()); dbClient.userGroupDao().deleteByUserId(dbSession, userId); diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java index 961b4825650..0c3179718c0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java @@ -123,8 +123,8 @@ public class SearchAction implements UsersWsAction { try (DbSession dbSession = dbClient.openSession(false)) { List logins = result.getDocs().stream().map(UserDoc::login).collect(toList()); Multimap groupsByLogin = dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, logins); - Map tokenCountsByLogin = dbClient.userTokenDao().countTokensByLogins(dbSession, logins); List users = dbClient.userDao().selectByOrderedLogins(dbSession, logins); + Map tokenCountsByLogin = dbClient.userTokenDao().countTokensByUsers(dbSession, users); Paging paging = forPageIndex(request.getPage()).withPageSize(request.getPageSize()).andTotal((int) result.getTotal()); return buildResponse(users, groupsByLogin, tokenCountsByLogin, fields, paging); } @@ -133,7 +133,7 @@ public class SearchAction implements UsersWsAction { private SearchWsResponse buildResponse(List users, Multimap groupsByLogin, Map tokenCountsByLogin, @Nullable List fields, Paging paging) { SearchWsResponse.Builder responseBuilder = newBuilder(); - users.forEach(user -> responseBuilder.addUsers(towsUser(user, firstNonNull(tokenCountsByLogin.get(user.getLogin()), 0), groupsByLogin.get(user.getLogin()), fields))); + users.forEach(user -> responseBuilder.addUsers(towsUser(user, firstNonNull(tokenCountsByLogin.get(user.getUuid()), 0), groupsByLogin.get(user.getLogin()), fields))); responseBuilder.getPagingBuilder() .setPageIndex(paging.pageIndex()) .setPageSize(paging.pageSize()) diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenAuthenticator.java index 6387a204023..f6c4f2e2e0b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenAuthenticator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenAuthenticator.java @@ -19,11 +19,13 @@ */ package org.sonar.server.usertoken; -import com.google.common.base.Optional; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.user.UserTokenDto; +import static java.util.Optional.empty; +import static java.util.Optional.of; + public class UserTokenAuthenticator { private final TokenGenerator tokenGenerator; private final DbClient dbClient; @@ -34,18 +36,18 @@ public class UserTokenAuthenticator { } /** - * Returns the user login if the token hash is found, else {@code Optional.absent()}. - * The returned login is not validated. If database is corrupted (table USER_TOKENS badly purged - * for instance), then the login may not relate to a valid user. + * Returns the user uuid if the token hash is found, else {@code Optional.absent()}. + * The returned uuid is not validated. If database is corrupted (table USER_TOKENS badly purged + * for instance), then the uuid may not relate to a valid user. */ public java.util.Optional authenticate(String token) { String tokenHash = tokenGenerator.hash(token); try (DbSession dbSession = dbClient.openSession(false)) { - Optional userToken = dbClient.userTokenDao().selectByTokenHash(dbSession, tokenHash); - if (userToken.isPresent()) { - return java.util.Optional.of(userToken.get().getLogin()); + UserTokenDto userToken = dbClient.userTokenDao().selectByTokenHash(dbSession, tokenHash); + if (userToken == null) { + return empty(); } - return java.util.Optional.empty(); + return of(userToken.getUserUuid()); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java index ab03cf56df4..cd1cfe07723 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java @@ -23,6 +23,7 @@ import org.sonar.core.platform.Module; import org.sonar.server.usertoken.ws.GenerateAction; import org.sonar.server.usertoken.ws.RevokeAction; import org.sonar.server.usertoken.ws.SearchAction; +import org.sonar.server.usertoken.ws.UserTokenSupport; import org.sonar.server.usertoken.ws.UserTokensWs; public class UserTokenModule extends Module { @@ -30,6 +31,7 @@ public class UserTokenModule extends Module { protected void configureModule() { add( UserTokensWs.class, + UserTokenSupport.class, GenerateAction.class, RevokeAction.class, SearchAction.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java index 7f880d4cf13..7b981fd017d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java @@ -19,7 +19,6 @@ */ package org.sonar.server.usertoken.ws; -import com.google.common.base.Optional; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -29,32 +28,32 @@ import org.sonar.db.DbSession; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTokenDto; import org.sonar.server.exceptions.ServerException; -import org.sonar.server.user.UserSession; import org.sonar.server.usertoken.TokenGenerator; import org.sonarqube.ws.UserTokens; import org.sonarqube.ws.UserTokens.GenerateWsResponse; import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; import static org.sonar.api.utils.DateUtils.formatDateTime; -import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.ACTION_GENERATE; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_LOGIN; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_NAME; +import static org.sonar.server.usertoken.ws.UserTokenSupport.ACTION_GENERATE; +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_LOGIN; +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_NAME; import static org.sonar.server.ws.WsUtils.checkRequest; import static org.sonar.server.ws.WsUtils.writeProtobuf; public class GenerateAction implements UserTokensWsAction { + private static final int MAX_TOKEN_NAME_LENGTH = 100; + private final DbClient dbClient; - private final UserSession userSession; private final System2 system; private final TokenGenerator tokenGenerator; + private final UserTokenSupport userTokenSupport; - public GenerateAction(DbClient dbClient, UserSession userSession, System2 system, TokenGenerator tokenGenerator) { - this.userSession = userSession; + public GenerateAction(DbClient dbClient, System2 system, TokenGenerator tokenGenerator, UserTokenSupport userTokenSupport) { this.dbClient = dbClient; this.system = system; this.tokenGenerator = tokenGenerator; + this.userTokenSupport = userTokenSupport; } @Override @@ -81,103 +80,61 @@ public class GenerateAction implements UserTokensWsAction { @Override public void handle(Request request, Response response) throws Exception { - UserTokens.GenerateWsResponse generateWsResponse = doHandle(toCreateWsRequest(request)); + UserTokens.GenerateWsResponse generateWsResponse = doHandle(request); writeProtobuf(generateWsResponse, request, response); } - private UserTokens.GenerateWsResponse doHandle(GenerateRequest request) { + private UserTokens.GenerateWsResponse doHandle(Request request) { try (DbSession dbSession = dbClient.openSession(false)) { - checkWsRequest(dbSession, request); - TokenPermissionsValidator.validate(userSession, request.getLogin()); + String name = getName(request); + UserDto user = userTokenSupport.getUser(dbSession, request); + checkTokenDoesNotAlreadyExists(dbSession, user, name); String token = tokenGenerator.generate(); String tokenHash = hashToken(dbSession, token); - - UserTokenDto userTokenDto = insertTokenInDb(dbSession, request, tokenHash); - - return buildResponse(userTokenDto, token); + UserTokenDto userTokenDto = insertTokenInDb(dbSession, user, name, tokenHash); + return buildResponse(userTokenDto, token, user); } } private String hashToken(DbSession dbSession, String token) { String tokenHash = tokenGenerator.hash(token); - Optional userToken = dbClient.userTokenDao().selectByTokenHash(dbSession, tokenHash); - if (userToken.isPresent()) { - throw new ServerException(HTTP_INTERNAL_ERROR, "Error while generating token. Please try again."); + UserTokenDto userToken = dbClient.userTokenDao().selectByTokenHash(dbSession, tokenHash); + if (userToken == null) { + return tokenHash; } - - return tokenHash; + throw new ServerException(HTTP_INTERNAL_ERROR, "Error while generating token. Please try again."); } - private void checkWsRequest(DbSession dbSession, GenerateRequest request) { - checkLoginExists(dbSession, request); - - Optional userTokenDto = dbClient.userTokenDao().selectByLoginAndName(dbSession, request.getLogin(), request.getName()); - checkRequest(!userTokenDto.isPresent(), "A user token with login '%s' and name '%s' already exists", request.getLogin(), request.getName()); + private void checkTokenDoesNotAlreadyExists(DbSession dbSession, UserDto user, String name) { + UserTokenDto userTokenDto = dbClient.userTokenDao().selectByUserAndName(dbSession, user, name); + checkRequest(userTokenDto == null, "A user token for login '%s' and name '%s' already exists", user.getLogin(), name); } - private void checkLoginExists(DbSession dbSession, GenerateRequest request) { - UserDto user = dbClient.userDao().selectByLogin(dbSession, request.getLogin()); - if (user == null) { - throw insufficientPrivilegesException(); - } + private static String getName(Request request) { + String name = request.mandatoryParam(PARAM_NAME).trim(); + checkRequest(!name.isEmpty(), "The '%s' parameter must not be blank", PARAM_NAME); + return name; } - private UserTokenDto insertTokenInDb(DbSession dbSession, GenerateRequest request, String tokenHash) { + private UserTokenDto insertTokenInDb(DbSession dbSession, UserDto user, String name, String tokenHash) { UserTokenDto userTokenDto = new UserTokenDto() - .setLogin(request.getLogin()) - .setName(request.getName()) + .setUserUuid(user.getUuid()) + .setName(name) .setTokenHash(tokenHash) .setCreatedAt(system.now()); - dbClient.userTokenDao().insert(dbSession, userTokenDto); dbSession.commit(); return userTokenDto; } - private GenerateRequest toCreateWsRequest(Request request) { - GenerateRequest generateWsRequest = new GenerateRequest() - .setLogin(request.param(PARAM_LOGIN)) - .setName(request.mandatoryParam(PARAM_NAME).trim()); - if (generateWsRequest.getLogin() == null) { - generateWsRequest.setLogin(userSession.getLogin()); - } - - checkRequest(!generateWsRequest.getName().isEmpty(), "The '%s' parameter must not be blank", PARAM_NAME); - - return generateWsRequest; - } - - private static GenerateWsResponse buildResponse(UserTokenDto userTokenDto, String token) { + private static GenerateWsResponse buildResponse(UserTokenDto userTokenDto, String token, UserDto user) { return UserTokens.GenerateWsResponse.newBuilder() - .setLogin(userTokenDto.getLogin()) + .setLogin(user.getLogin()) .setName(userTokenDto.getName()) .setCreatedAt(formatDateTime(userTokenDto.getCreatedAt())) .setToken(token) .build(); } - private static class GenerateRequest { - - private String login; - private String name; - - public GenerateRequest setLogin(String login) { - this.login = login; - return this; - } - - public String getLogin() { - return login; - } - - public GenerateRequest setName(String name) { - this.name = name; - return this; - } - - public String getName() { - return name; - } - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/RevokeAction.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/RevokeAction.java index 303e29fdd63..20e6e116fff 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/RevokeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/RevokeAction.java @@ -24,25 +24,26 @@ import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; import org.sonar.db.DbSession; -import org.sonar.server.user.UserSession; +import org.sonar.db.user.UserDto; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.ACTION_REVOKE; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_LOGIN; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_NAME; +import static org.sonar.server.usertoken.ws.UserTokenSupport.ACTION_REVOKE; +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_LOGIN; +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_NAME; public class RevokeAction implements UserTokensWsAction { + private final DbClient dbClient; - private final UserSession userSession; + private final UserTokenSupport userTokenSupport; - public RevokeAction(DbClient dbClient, UserSession userSession) { + public RevokeAction(DbClient dbClient, UserTokenSupport userTokenSupport) { this.dbClient = dbClient; - this.userSession = userSession; + this.userTokenSupport = userTokenSupport; } @Override public void define(WebService.NewController context) { WebService.NewAction action = context.createAction(ACTION_REVOKE) - .setDescription("Revoke a user access token.
"+ + .setDescription("Revoke a user access token.
" + "If the login is set, it requires administration permissions. Otherwise, a token is generated for the authenticated user.") .setSince("5.3") .setPost(true) @@ -60,16 +61,10 @@ public class RevokeAction implements UserTokensWsAction { @Override public void handle(Request request, Response response) throws Exception { - String login = request.param(PARAM_LOGIN); - if (login == null) { - login = userSession.getLogin(); - } String name = request.mandatoryParam(PARAM_NAME); - - TokenPermissionsValidator.validate(userSession, login); - try (DbSession dbSession = dbClient.openSession(false)) { - dbClient.userTokenDao().deleteByLoginAndName(dbSession, login, name); + UserDto user = userTokenSupport.getUser(dbSession, request); + dbClient.userTokenDao().deleteByUserAndName(dbSession, user, name); dbSession.commit(); } response.noContent(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/SearchAction.java index 4ead6902c20..87870d46579 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/SearchAction.java @@ -25,23 +25,23 @@ import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTokenDto; -import org.sonar.server.user.UserSession; import org.sonarqube.ws.UserTokens.SearchWsResponse; import static org.sonar.api.utils.DateUtils.formatDateTime; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.ACTION_SEARCH; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_LOGIN; -import static org.sonar.server.ws.WsUtils.checkFound; +import static org.sonar.server.usertoken.ws.UserTokenSupport.ACTION_SEARCH; +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_LOGIN; import static org.sonar.server.ws.WsUtils.writeProtobuf; public class SearchAction implements UserTokensWsAction { + private final DbClient dbClient; - private final UserSession userSession; + private final UserTokenSupport userTokenSupport; - public SearchAction(DbClient dbClient, UserSession userSession) { + public SearchAction(DbClient dbClient, UserTokenSupport userTokenSupport) { this.dbClient = dbClient; - this.userSession = userSession; + this.userTokenSupport = userTokenSupport; } @Override @@ -61,28 +61,22 @@ public class SearchAction implements UserTokensWsAction { @Override public void handle(Request request, Response response) throws Exception { - String login = request.param(PARAM_LOGIN); - if (login == null) { - login = userSession.getLogin(); - } - SearchWsResponse searchWsResponse = doHandle(login); + SearchWsResponse searchWsResponse = doHandle(request); writeProtobuf(searchWsResponse, request, response); } - private SearchWsResponse doHandle(String login) { - TokenPermissionsValidator.validate(userSession, login); - + private SearchWsResponse doHandle(Request request) { try (DbSession dbSession = dbClient.openSession(false)) { - checkLoginExists(dbSession, login); - List userTokens = dbClient.userTokenDao().selectByLogin(dbSession, login); - return buildResponse(login, userTokens); + UserDto user = userTokenSupport.getUser(dbSession, request); + List userTokens = dbClient.userTokenDao().selectByUser(dbSession, user); + return buildResponse(user, userTokens); } } - private static SearchWsResponse buildResponse(String login, List userTokensDto) { + private static SearchWsResponse buildResponse(UserDto user, List userTokensDto) { SearchWsResponse.Builder searchWsResponse = SearchWsResponse.newBuilder(); SearchWsResponse.UserToken.Builder userTokenBuilder = SearchWsResponse.UserToken.newBuilder(); - searchWsResponse.setLogin(login); + searchWsResponse.setLogin(user.getLogin()); for (UserTokenDto userTokenDto : userTokensDto) { userTokenBuilder .clear() @@ -94,7 +88,4 @@ public class SearchAction implements UserTokensWsAction { return searchWsResponse.build(); } - private void checkLoginExists(DbSession dbSession, String login) { - checkFound(dbClient.userDao().selectByLogin(dbSession, login), "User with login '%s' not found", login); - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/TokenPermissionsValidator.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/TokenPermissionsValidator.java deleted file mode 100644 index 5aec8108892..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/TokenPermissionsValidator.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.usertoken.ws; - -import javax.annotation.Nullable; -import org.sonar.server.user.UserSession; - -import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; - -class TokenPermissionsValidator { - private TokenPermissionsValidator() { - // prevent instantiation - } - - static void validate(UserSession userSession, @Nullable String requestLogin) { - userSession.checkLoggedIn(); - if (!userSession.isSystemAdministrator() && !isLoggedInUser(userSession, requestLogin)) { - throw insufficientPrivilegesException(); - } - } - - private static boolean isLoggedInUser(UserSession userSession, @Nullable String requestLogin) { - return requestLogin != null && requestLogin.equals(userSession.getLogin()); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokenSupport.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokenSupport.java new file mode 100644 index 00000000000..be5545fd581 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokenSupport.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.usertoken.ws; + +import javax.annotation.Nullable; +import org.sonar.api.server.ws.Request; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.user.UserDto; +import org.sonar.server.user.UserSession; + +import static java.util.Objects.requireNonNull; +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; +import static org.sonar.server.ws.WsUtils.checkFound; + +public class UserTokenSupport { + + static final String CONTROLLER = "api/user_tokens"; + static final String ACTION_SEARCH = "search"; + static final String ACTION_REVOKE = "revoke"; + static final String ACTION_GENERATE = "generate"; + static final String PARAM_LOGIN = "login"; + static final String PARAM_NAME = "name"; + + private final DbClient dbClient; + private final UserSession userSession; + + public UserTokenSupport(DbClient dbClient, UserSession userSession) { + this.dbClient = dbClient; + this.userSession = userSession; + } + + UserDto getUser(DbSession dbSession, Request request) { + String login = request.param(PARAM_LOGIN); + login = login == null ? userSession.getLogin() : login; + validate(userSession, login); + UserDto user = dbClient.userDao().selectByLogin(dbSession, requireNonNull(login, "Login should not be null")); + checkFound(user, "User with login '%s' doesn't exist", login); + return user; + } + + private static void validate(UserSession userSession, @Nullable String requestLogin) { + userSession.checkLoggedIn(); + if (userSession.isSystemAdministrator() || isLoggedInUser(userSession, requestLogin)) { + return; + } + throw insufficientPrivilegesException(); + } + + private static boolean isLoggedInUser(UserSession userSession, @Nullable String requestLogin) { + return requestLogin != null && requestLogin.equals(userSession.getLogin()); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java index 7cd28cff6ea..60888d55e01 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java @@ -21,7 +21,7 @@ package org.sonar.server.usertoken.ws; import org.sonar.api.server.ws.WebService; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.CONTROLLER; +import static org.sonar.server.usertoken.ws.UserTokenSupport.CONTROLLER; public class UserTokensWs implements WebService { private final UserTokensWsAction[] actions; diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWsParameters.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWsParameters.java index d1d41d6426a..3d0fb5e16bc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWsParameters.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWsParameters.java @@ -20,13 +20,6 @@ package org.sonar.server.usertoken.ws; public class UserTokensWsParameters { - public static final String CONTROLLER = "api/user_tokens"; - public static final String ACTION_GENERATE = "generate"; - public static final String ACTION_REVOKE = "revoke"; - public static final String ACTION_SEARCH = "search"; - - public static final String PARAM_LOGIN = "login"; - public static final String PARAM_NAME = "name"; private UserTokensWsParameters() { // constants only diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java index 8d89727aaa3..17bc1f09288 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java @@ -62,11 +62,11 @@ public class BasicAuthenticatorTest { public ExpectedException expectedException = none(); @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); + public DbTester db = DbTester.create(System2.INSTANCE); - private DbClient dbClient = dbTester.getDbClient(); + private DbClient dbClient = db.getDbClient(); - private DbSession dbSession = dbTester.getSession(); + private DbSession dbSession = db.getSession(); private CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class); private UserTokenAuthenticator userTokenAuthenticator = mock(UserTokenAuthenticator.class); @@ -139,20 +139,19 @@ public class BasicAuthenticatorTest { @Test public void authenticate_from_user_token() { - insertUser(UserTesting.newUserDto().setLogin(LOGIN)); - when(userTokenAuthenticator.authenticate("token")).thenReturn(Optional.of(LOGIN)); + UserDto user = db.users().insertUser(); + when(userTokenAuthenticator.authenticate("token")).thenReturn(Optional.of(user.getUuid())); when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); - Optional userDto = underTest.authenticate(request); + Optional userAuthenticated = underTest.authenticate(request); - assertThat(userDto.isPresent()).isTrue(); - assertThat(userDto.get().getLogin()).isEqualTo(LOGIN); - verify(authenticationEvent).loginSuccess(request, LOGIN, Source.local(BASIC_TOKEN)); + assertThat(userAuthenticated.isPresent()).isTrue(); + assertThat(userAuthenticated.get().getLogin()).isEqualTo(user.getLogin()); + verify(authenticationEvent).loginSuccess(request, user.getLogin(), Source.local(BASIC_TOKEN)); } @Test public void does_not_authenticate_from_user_token_when_token_is_invalid() { - insertUser(UserTesting.newUserDto().setLogin(LOGIN)); when(userTokenAuthenticator.authenticate("token")).thenReturn(Optional.empty()); when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); @@ -165,8 +164,7 @@ public class BasicAuthenticatorTest { } @Test - public void does_not_authenticate_from_user_token_when_token_does_not_match_active_user() { - insertUser(UserTesting.newUserDto().setLogin(LOGIN)); + public void does_not_authenticate_from_user_token_when_token_does_not_match_existing_user() { when(userTokenAuthenticator.authenticate("token")).thenReturn(Optional.of("Unknown user")); when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); @@ -178,10 +176,18 @@ public class BasicAuthenticatorTest { } } - private UserDto insertUser(UserDto userDto) { - dbClient.userDao().insert(dbSession, userDto); - dbSession.commit(); - return userDto; + @Test + public void does_not_authenticate_from_user_token_when_token_does_not_match_active_user() { + UserDto user = db.users().insertDisabledUser(); + when(userTokenAuthenticator.authenticate("token")).thenReturn(Optional.of(user.getUuid())); + when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); + + expectedException.expect(authenticationException().from(Source.local(Method.BASIC_TOKEN)).withoutLogin().andNoPublicMessage()); + try { + underTest.authenticate(request); + } finally { + verifyZeroInteractions(authenticationEvent); + } } private static String toBase64(String text) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java index c5379de6bb3..04e4126c093 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java @@ -20,6 +20,7 @@ package org.sonar.server.user.ws; import java.util.Optional; +import java.util.function.Consumer; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -62,8 +63,6 @@ import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES; import static org.sonar.db.permission.OrganizationPermission.SCAN; import static org.sonar.db.property.PropertyTesting.newUserPropertyDto; -import static org.sonar.db.user.UserTesting.newUserDto; -import static org.sonar.db.user.UserTokenTesting.newUserToken; import static org.sonar.server.user.index.UserIndexDefinition.FIELD_ACTIVE; import static org.sonar.server.user.index.UserIndexDefinition.FIELD_UUID; import static org.sonar.test.JsonAssert.assertJson; @@ -94,7 +93,7 @@ public class DeactivateActionTest { @Test public void deactivate_user_and_delete_his_related_data() { - UserDto user = insertUser(newUserDto() + UserDto user = insertUser(u -> u .setLogin("ada.lovelace") .setEmail("ada.lovelace@noteg.com") .setName("Ada Lovelace") @@ -114,7 +113,7 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_group_membership() { logInAsSystemAdministrator(); - UserDto user = insertUser(newUserDto()); + UserDto user = insertUser(); GroupDto group1 = db.users().insertGroup(); db.users().insertGroup(); db.users().insertMember(group1, user); @@ -127,20 +126,20 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_tokens() { logInAsSystemAdministrator(); - UserDto user = insertUser(newUserDto()); - db.getDbClient().userTokenDao().insert(dbSession, newUserToken().setLogin(user.getLogin())); - db.getDbClient().userTokenDao().insert(dbSession, newUserToken().setLogin(user.getLogin())); + UserDto user = insertUser(); + db.users().insertToken(user); + db.users().insertToken(user); db.commit(); deactivate(user.getLogin()).getInput(); - assertThat(db.getDbClient().userTokenDao().selectByLogin(dbSession, user.getLogin())).isEmpty(); + assertThat(db.getDbClient().userTokenDao().selectByUser(dbSession, user)).isEmpty(); } @Test public void deactivate_user_deletes_his_properties() { logInAsSystemAdministrator(); - UserDto user = insertUser(newUserDto()); + UserDto user = insertUser(); ComponentDto project = db.components().insertPrivateProject(); db.properties().insertProperty(newUserPropertyDto(user)); db.properties().insertProperty(newUserPropertyDto(user)); @@ -155,7 +154,7 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_permissions() { logInAsSystemAdministrator(); - UserDto user = insertUser(newUserDto()); + UserDto user = insertUser(); ComponentDto project = db.components().insertPrivateProject(); db.users().insertPermissionOnUser(user, SCAN); db.users().insertPermissionOnUser(user, ADMINISTER_QUALITY_PROFILES); @@ -171,7 +170,7 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_permission_templates() { logInAsSystemAdministrator(); - UserDto user = insertUser(newUserDto()); + UserDto user = insertUser(); PermissionTemplateDto template = db.permissionTemplates().insertTemplate(); PermissionTemplateDto anotherTemplate = db.permissionTemplates().insertTemplate(); db.permissionTemplates().addUserToTemplate(template.getId(), user.getId(), USER); @@ -187,7 +186,7 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_qprofiles_permissions() { logInAsSystemAdministrator(); - UserDto user = insertUser(newUserDto()); + UserDto user = insertUser(); QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization()); db.qualityProfiles().addUserPermission(profile, user); @@ -199,7 +198,7 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_default_assignee_settings() { logInAsSystemAdministrator(); - UserDto user = insertUser(newUserDto()); + UserDto user = insertUser(); ComponentDto project = db.components().insertPrivateProject(); ComponentDto anotherProject = db.components().insertPrivateProject(); db.properties().insertProperty(new PropertyDto().setKey("sonar.issues.defaultAssigneeLogin").setValue(user.getLogin()).setResourceId(project.getId())); @@ -215,7 +214,7 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_organization_membership() { logInAsSystemAdministrator(); - UserDto user = insertUser(newUserDto()); + UserDto user = insertUser(); OrganizationDto organization = db.organizations().insert(); db.organizations().addMember(organization, user); OrganizationDto anotherOrganization = db.organizations().insert(); @@ -294,7 +293,7 @@ public class DeactivateActionTest { public void fail_to_deactivate_last_administrator_of_organization() { // user1 is the unique administrator of org1 and org2. // user1 and user2 are both administrators of org3 - UserDto user1 = insertUser(newUserDto().setLogin("test")); + UserDto user1 = insertUser(u -> u.setLogin("test")); OrganizationDto org1 = db.organizations().insert(newOrganizationDto().setKey("org1")); OrganizationDto org2 = db.organizations().insert(newOrganizationDto().setKey("org2")); OrganizationDto org3 = db.organizations().insert(newOrganizationDto().setKey("org3")); @@ -335,7 +334,7 @@ public class DeactivateActionTest { @Test public void test_example() { - UserDto user = insertUser(newUserDto() + UserDto user = insertUser(u -> u .setLogin("ada.lovelace") .setEmail("ada.lovelace@noteg.com") .setName("Ada Lovelace") @@ -349,13 +348,14 @@ public class DeactivateActionTest { } private UserDto createUser() { - return insertUser(newUserDto()); + return insertUser(); } - private UserDto insertUser(UserDto user) { - dbClient.userDao().insert(dbSession, user); - dbClient.userTokenDao().insert(dbSession, newUserToken().setLogin(user.getLogin())); - dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setUserId(user.getId()).setKey("foo").setValue("bar")); + @SafeVarargs + private final UserDto insertUser(Consumer... populators) { + UserDto user = db.users().insertUser(populators); + db.users().insertToken(user); + db.properties().insertProperties(new PropertyDto().setUserId(user.getId()).setKey("foo").setValue("bar")); userIndexer.commitAndIndex(dbSession, user); return user; } diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java index 86f1d5a258f..2e6f056e861 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java @@ -22,6 +22,7 @@ package org.sonar.server.user.ws; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import java.util.stream.IntStream; import org.junit.Rule; import org.junit.Test; import org.sonar.api.server.ws.WebService.Param; @@ -32,7 +33,6 @@ import org.sonar.db.DbTester; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserGroupDto; -import org.sonar.db.user.UserTesting; import org.sonar.server.es.EsTester; import org.sonar.server.issue.ws.AvatarResolverImpl; import org.sonar.server.tester.UserSessionRule; @@ -45,7 +45,6 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.db.user.GroupTesting.newGroupDto; -import static org.sonar.db.user.UserTokenTesting.newUserToken; import static org.sonar.test.JsonAssert.assertJson; public class SearchActionTest { @@ -86,12 +85,11 @@ public class SearchActionTest { dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setUserId(simon.getId()).setGroupId(sonarUsers.getId())); dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setUserId(fmallet.getId()).setGroupId(sonarUsers.getId())); dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setUserId(fmallet.getId()).setGroupId(sonarAdministrators.getId())); - + db.commit(); for (int i = 0; i < 3; i++) { - dbClient.userTokenDao().insert(dbSession, newUserToken().setLogin(simon.getLogin())); + db.users().insertToken(simon); } - dbClient.userTokenDao().insert(dbSession, newUserToken().setLogin(fmallet.getLogin())); - db.commit(); + db.users().insertToken(fmallet); userIndexer.indexOnStartup(null); loginAsSystemAdministrator(); @@ -297,15 +295,10 @@ public class SearchActionTest { .setExternalIdentityProvider("sonarqube")); userDtos.add(userDto); - for (int tokenIndex = 0; tokenIndex < index; tokenIndex++) { - dbClient.userTokenDao().insert(dbSession, newUserToken() - .setLogin(login) - .setName(String.format("%s-%d", login, tokenIndex))); - } + IntStream.range(0, index).forEach(i -> db.users().insertToken(userDto, t -> t.setName(String.format("%s-%d", login, i)))); db.users().insertMember(group1, userDto); db.users().insertMember(group2, userDto); } - dbSession.commit(); userIndexer.indexOnStartup(null); return userDtos; } diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticatorTest.java index 01578f9dc70..611a4553e1d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticatorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticatorTest.java @@ -19,48 +19,44 @@ */ package org.sonar.server.usertoken; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.db.user.UserTokenTesting.newUserToken; - import java.util.Optional; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import org.sonar.db.user.UserDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class UserTokenAuthenticatorTest { - static final String GRACE_HOPPER = "grace.hopper"; - static final String ADA_LOVELACE = "ada.lovelace"; @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule public DbTester db = DbTester.create(System2.INSTANCE); - DbClient dbClient = db.getDbClient(); - DbSession dbSession = db.getSession(); - TokenGenerator tokenGenerator = mock(TokenGenerator.class); - UserTokenAuthenticator underTest = new UserTokenAuthenticator(tokenGenerator, db.getDbClient()); + private TokenGenerator tokenGenerator = mock(TokenGenerator.class); + + private UserTokenAuthenticator underTest = new UserTokenAuthenticator(tokenGenerator, db.getDbClient()); @Test public void return_login_when_token_hash_found_in_db() { String token = "known-token"; String tokenHash = "123456789"; when(tokenGenerator.hash(token)).thenReturn(tokenHash); - dbClient.userTokenDao().insert(dbSession, newUserToken().setLogin(GRACE_HOPPER).setTokenHash(tokenHash)); - dbClient.userTokenDao().insert(dbSession, newUserToken().setLogin(ADA_LOVELACE).setTokenHash("another-token-hash")); - db.commit(); + UserDto user1 = db.users().insertUser(); + db.users().insertToken(user1, t -> t.setTokenHash(tokenHash)); + UserDto user2 = db.users().insertUser(); + db.users().insertToken(user2, t -> t.setTokenHash("another-token-hash")); Optional login = underTest.authenticate(token); assertThat(login.isPresent()).isTrue(); - assertThat(login.get()).isEqualTo(GRACE_HOPPER); + assertThat(login.get()).isEqualTo(user1.getUuid()); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java index 3f98207a110..657ffb14b0e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java @@ -29,6 +29,6 @@ public class UserTokenModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new UserTokenModule().configure(container); - assertThat(container.size()).isEqualTo(8); + assertThat(container.size()).isEqualTo(9); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java index 8753fdb3680..5beeeb5ba3e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java @@ -26,8 +26,10 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.System2; import org.sonar.db.DbTester; +import org.sonar.db.user.UserDto; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.ServerException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.tester.UserSessionRule; @@ -41,15 +43,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.sonar.db.user.UserTesting.newUserDto; -import static org.sonar.db.user.UserTokenTesting.newUserToken; +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_LOGIN; +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_NAME; import static org.sonar.test.JsonAssert.assertJson; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_LOGIN; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_NAME; public class GenerateActionTest { - private static final String GRACE_HOPPER = "grace.hopper"; - private static final String ADA_LOVELACE = "ada.lovelace"; + private static final String TOKEN_NAME = "Third Party Application"; @Rule @@ -60,26 +59,25 @@ public class GenerateActionTest { public ExpectedException expectedException = ExpectedException.none(); private TokenGenerator tokenGenerator = mock(TokenGenerator.class); - private WsActionTester ws; + + private WsActionTester ws = new WsActionTester( + new GenerateAction(db.getDbClient(), System2.INSTANCE, tokenGenerator, new UserTokenSupport(db.getDbClient(), userSession))); @Before public void setUp() { when(tokenGenerator.generate()).thenReturn("123456789"); when(tokenGenerator.hash(anyString())).thenReturn("987654321"); - db.users().insertUser(newUserDto().setLogin(GRACE_HOPPER)); - db.users().insertUser(newUserDto().setLogin(ADA_LOVELACE)); - - ws = new WsActionTester( - new GenerateAction(db.getDbClient(), userSession, System2.INSTANCE, tokenGenerator)); } @Test public void json_example() { + UserDto user1 = db.users().insertUser(u -> u.setLogin("grace.hopper")); + UserDto user2 = db.users().insertUser(u -> u.setLogin("ada.lovelace")); logInAsSystemAdministrator(); String response = ws.newRequest() .setMediaType(MediaTypes.JSON) - .setParam(PARAM_LOGIN, GRACE_HOPPER) + .setParam(PARAM_LOGIN, user1.getLogin()) .setParam(PARAM_NAME, TOKEN_NAME) .execute().getInput(); @@ -88,11 +86,12 @@ public class GenerateActionTest { @Test public void a_user_can_generate_token_for_himself() { - userSession.logIn(GRACE_HOPPER); + UserDto user = db.users().insertUser(); + userSession.logIn(user); GenerateWsResponse response = newRequest(null, TOKEN_NAME); - assertThat(response.getLogin()).isEqualTo(GRACE_HOPPER); + assertThat(response.getLogin()).isEqualTo(user.getLogin()); assertThat(response.getCreatedAt()).isNotEmpty(); } @@ -100,61 +99,66 @@ public class GenerateActionTest { public void fail_if_login_does_not_exist() { logInAsSystemAdministrator(); - expectedException.expect(ForbiddenException.class); + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("User with login 'unknown-login' doesn't exist"); newRequest("unknown-login", "any-name"); } @Test public void fail_if_name_is_blank() { + UserDto user = db.users().insertUser(); logInAsSystemAdministrator(); expectedException.expect(BadRequestException.class); expectedException.expectMessage("The 'name' parameter must not be blank"); - newRequest(GRACE_HOPPER, " "); + newRequest(user.getLogin(), " "); } @Test public void fail_if_token_with_same_login_and_name_exists() { + UserDto user = db.users().insertUser(); logInAsSystemAdministrator(); + db.users().insertToken(user, t -> t.setName(TOKEN_NAME)); - newRequest(GRACE_HOPPER, TOKEN_NAME); expectedException.expect(BadRequestException.class); - expectedException.expectMessage("A user token with login 'grace.hopper' and name 'Third Party Application' already exists"); + expectedException.expectMessage(String.format("A user token for login '%s' and name 'Third Party Application' already exists", user.getLogin())); - newRequest(GRACE_HOPPER, TOKEN_NAME); + newRequest(user.getLogin(), TOKEN_NAME); } @Test public void fail_if_token_hash_already_exists_in_db() { + UserDto user = db.users().insertUser(); logInAsSystemAdministrator(); - when(tokenGenerator.hash(anyString())).thenReturn("987654321"); - db.getDbClient().userTokenDao().insert(db.getSession(), newUserToken().setTokenHash("987654321")); - db.commit(); + db.users().insertToken(user, t -> t.setTokenHash("987654321")); + expectedException.expect(ServerException.class); expectedException.expectMessage("Error while generating token. Please try again."); - newRequest(GRACE_HOPPER, TOKEN_NAME); + newRequest(user.getLogin(), TOKEN_NAME); } @Test public void throw_ForbiddenException_if_non_administrator_creates_token_for_someone_else() { + UserDto user = db.users().insertUser(); userSession.logIn().setNonSystemAdministrator(); expectedException.expect(ForbiddenException.class); - newRequest(GRACE_HOPPER, TOKEN_NAME); + newRequest(user.getLogin(), TOKEN_NAME); } @Test public void throw_UnauthorizedException_if_not_logged_in() { + UserDto user = db.users().insertUser(); userSession.anonymous(); expectedException.expect(UnauthorizedException.class); - newRequest(GRACE_HOPPER, TOKEN_NAME); + newRequest(user.getLogin(), TOKEN_NAME); } private GenerateWsResponse newRequest(@Nullable String login, String name) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/RevokeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/RevokeActionTest.java index 1ae6a1f0ba5..8e0611f9b09 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/RevokeActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/RevokeActionTest.java @@ -20,7 +20,6 @@ package org.sonar.server.usertoken.ws; import javax.annotation.Nullable; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -28,23 +27,20 @@ import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTokenDto; import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.db.user.UserTokenTesting.newUserToken; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_LOGIN; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_NAME; - +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_LOGIN; +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_NAME; public class RevokeActionTest { - private static final String GRACE_HOPPER = "grace.hopper"; - private static final String ADA_LOVELACE = "ada.lovelace"; - private static final String TOKEN_NAME = "token-name"; @Rule public DbTester db = DbTester.create(System2.INSTANCE); @@ -54,67 +50,82 @@ public class RevokeActionTest { public ExpectedException expectedException = ExpectedException.none(); private DbClient dbClient = db.getDbClient(); - private final DbSession dbSession = db.getSession(); - private WsActionTester ws; - - @Before - public void setUp() { - ws = new WsActionTester( - new RevokeAction(dbClient, userSession)); - } + private DbSession dbSession = db.getSession(); + private WsActionTester ws = new WsActionTester(new RevokeAction(dbClient, new UserTokenSupport(db.getDbClient(), userSession))); @Test public void delete_token_in_db() { logInAsSystemAdministrator(); - insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName("token-to-delete")); - insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName("token-to-keep-1")); - insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName("token-to-keep-2")); - insertUserToken(newUserToken().setLogin(ADA_LOVELACE).setName("token-to-delete")); + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + UserTokenDto tokenToDelete = db.users().insertToken(user1); + UserTokenDto tokenToKeep1 = db.users().insertToken(user1); + UserTokenDto tokenToKeep2 = db.users().insertToken(user1); + UserTokenDto tokenFromAnotherUser = db.users().insertToken(user2); - String response = newRequest(GRACE_HOPPER, "token-to-delete"); + String response = newRequest(user1.getLogin(), tokenToDelete.getName()); assertThat(response).isEmpty(); - assertThat(dbClient.userTokenDao().selectByLogin(dbSession, GRACE_HOPPER)).extracting("name").containsOnly("token-to-keep-1", "token-to-keep-2"); - assertThat(dbClient.userTokenDao().selectByLogin(dbSession, ADA_LOVELACE)).extracting("name").containsOnly("token-to-delete"); + assertThat(dbClient.userTokenDao().selectByUser(dbSession, user1)) + .extracting(UserTokenDto::getName) + .containsExactlyInAnyOrder(tokenToKeep1.getName(), tokenToKeep2.getName()); + assertThat(dbClient.userTokenDao().selectByUser(dbSession, user2)) + .extracting(UserTokenDto::getName) + .containsExactlyInAnyOrder(tokenFromAnotherUser.getName()); } @Test public void user_can_delete_its_own_tokens() { - userSession.logIn(GRACE_HOPPER); - insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName("token-to-delete")); + UserDto user = db.users().insertUser(); + UserTokenDto token = db.users().insertToken(user); + userSession.logIn(user); - String response = newRequest(null, "token-to-delete"); + String response = newRequest(null, token.getName()); assertThat(response).isEmpty(); - assertThat(dbClient.userTokenDao().selectByLogin(dbSession, GRACE_HOPPER)).isEmpty(); + assertThat(dbClient.userTokenDao().selectByUser(dbSession, user)).isEmpty(); } @Test public void does_not_fail_when_incorrect_login_or_name() { + UserDto user = db.users().insertUser(); + db.users().insertToken(user); + logInAsSystemAdministrator(); - insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName(TOKEN_NAME)); - newRequest(ADA_LOVELACE, "another-token-name"); + newRequest(user.getLogin(), "another-token-name"); } @Test public void throw_ForbiddenException_if_non_administrator_revokes_token_of_someone_else() { + UserDto user = db.users().insertUser(); + UserTokenDto token = db.users().insertToken(user); userSession.logIn(); - insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName(TOKEN_NAME)); expectedException.expect(ForbiddenException.class); - newRequest(GRACE_HOPPER, TOKEN_NAME); + newRequest(user.getLogin(), token.getName()); } @Test public void throw_UnauthorizedException_if_not_logged_in() { + UserDto user = db.users().insertUser(); + UserTokenDto token = db.users().insertToken(user); userSession.anonymous(); - insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName(TOKEN_NAME)); expectedException.expect(UnauthorizedException.class); - newRequest(GRACE_HOPPER, TOKEN_NAME); + newRequest(user.getLogin(), token.getName()); + } + + @Test + public void fail_if_login_does_not_exist() { + logInAsSystemAdministrator(); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("User with login 'unknown-login' doesn't exist"); + + newRequest("unknown-login", "any-name"); } private String newRequest(@Nullable String login, String name) { @@ -127,11 +138,6 @@ public class RevokeActionTest { return testRequest.execute().getInput(); } - private void insertUserToken(UserTokenDto userToken) { - dbClient.userTokenDao().insert(dbSession, userToken); - dbSession.commit(); - } - private void logInAsSystemAdministrator() { userSession.logIn().setSystemAdministrator(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java index 69f936f4220..9bf8272416d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java @@ -20,14 +20,13 @@ package org.sonar.server.usertoken.ws; import javax.annotation.Nullable; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.System2; import org.sonar.db.DbClient; -import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import org.sonar.db.user.UserDto; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; @@ -37,14 +36,10 @@ import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.UserTokens.SearchWsResponse; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.db.user.UserTesting.newUserDto; -import static org.sonar.db.user.UserTokenTesting.newUserToken; +import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_LOGIN; import static org.sonar.test.JsonAssert.assertJson; -import static org.sonar.server.usertoken.ws.UserTokensWsParameters.PARAM_LOGIN; public class SearchActionTest { - private static final String GRACE_HOPPER = "grace.hopper"; - private static final String ADA_LOVELACE = "ada.lovelace"; @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -52,40 +47,22 @@ public class SearchActionTest { public UserSessionRule userSession = UserSessionRule.standalone(); @Rule public DbTester db = DbTester.create(System2.INSTANCE); - private DbClient dbClient = db.getDbClient(); - private DbSession dbSession = db.getSession(); - private WsActionTester ws = new WsActionTester(new SearchAction(dbClient, userSession)); - @Before - public void setUp() { - db.users().insertUser(newUserDto().setLogin(GRACE_HOPPER)); - db.users().insertUser(newUserDto().setLogin(ADA_LOVELACE)); - } + private DbClient dbClient = db.getDbClient(); + private WsActionTester ws = new WsActionTester(new SearchAction(dbClient, new UserTokenSupport(db.getDbClient(), userSession))); @Test public void search_json_example() { + UserDto user1 = db.users().insertUser(u -> u.setLogin("grace.hopper")); + UserDto user2 = db.users().insertUser(u -> u.setLogin("ada.lovelace")); + db.users().insertToken(user1, t -> t.setName("Project scan on Travis").setCreatedAt(1448523067221L)); + db.users().insertToken(user1, t -> t.setName("Project scan on AppVeyor").setCreatedAt(1438523067221L)); + db.users().insertToken(user1, t -> t.setName("Project scan on Jenkins").setCreatedAt(1428523067221L)); + db.users().insertToken(user2, t -> t.setName("Project scan on Travis").setCreatedAt(141456787123L)); logInAsSystemAdministrator(); - dbClient.userTokenDao().insert(dbSession, newUserToken() - .setCreatedAt(1448523067221L) - .setName("Project scan on Travis") - .setLogin(GRACE_HOPPER)); - dbClient.userTokenDao().insert(dbSession, newUserToken() - .setCreatedAt(1438523067221L) - .setName("Project scan on AppVeyor") - .setLogin(GRACE_HOPPER)); - dbClient.userTokenDao().insert(dbSession, newUserToken() - .setCreatedAt(1428523067221L) - .setName("Project scan on Jenkins") - .setLogin(GRACE_HOPPER)); - dbClient.userTokenDao().insert(dbSession, newUserToken() - .setCreatedAt(141456787123L) - .setName("Project scan on Travis") - .setLogin(ADA_LOVELACE)); - dbSession.commit(); - String response = ws.newRequest() - .setParam(PARAM_LOGIN, GRACE_HOPPER) + .setParam(PARAM_LOGIN, user1.getLogin()) .execute().getInput(); assertJson(response).isSimilarTo(getClass().getResource("search-example.json")); @@ -93,12 +70,9 @@ public class SearchActionTest { @Test public void a_user_can_search_its_own_token() { - userSession.logIn(GRACE_HOPPER); - dbClient.userTokenDao().insert(dbSession, newUserToken() - .setCreatedAt(1448523067221L) - .setName("Project scan on Travis") - .setLogin(GRACE_HOPPER)); - db.commit(); + UserDto user = db.users().insertUser(); + db.users().insertToken(user, t -> t.setName("Project scan on Travis").setCreatedAt(1448523067221L)); + userSession.logIn(user); SearchWsResponse response = newRequest(null); @@ -110,27 +84,29 @@ public class SearchActionTest { logInAsSystemAdministrator(); expectedException.expect(NotFoundException.class); - expectedException.expectMessage("User with login 'unknown-login' not found"); + expectedException.expectMessage("User with login 'unknown-login' doesn't exist"); newRequest("unknown-login"); } @Test public void throw_ForbiddenException_if_a_non_root_administrator_searches_for_tokens_of_someone_else() { + UserDto user = db.users().insertUser(); userSession.logIn(); expectedException.expect(ForbiddenException.class); - newRequest(GRACE_HOPPER); + newRequest(user.getLogin()); } @Test public void throw_UnauthorizedException_if_not_logged_in() { + UserDto user = db.users().insertUser(); userSession.anonymous(); expectedException.expect(UnauthorizedException.class); - newRequest(GRACE_HOPPER); + newRequest(user.getLogin()); } private SearchWsResponse newRequest(@Nullable String login) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java index cb8284242a8..c93fce55d9a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java @@ -24,7 +24,6 @@ import org.junit.Test; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.db.DbClient; -import org.sonar.server.user.UserSession; import org.sonar.server.usertoken.TokenGenerator; import org.sonar.server.ws.WsTester; @@ -38,15 +37,15 @@ public class UserTokensWsTest { @Before public void setUp() { - UserSession userSession = mock(UserSession.class); DbClient dbClient = mock(DbClient.class); System2 system = mock(System2.class); TokenGenerator tokenGenerator = mock(TokenGenerator.class); + UserTokenSupport userTokenSupport = mock(UserTokenSupport.class); ws = new WsTester(new UserTokensWs( - new GenerateAction(dbClient, userSession, system, tokenGenerator), - new RevokeAction(dbClient, userSession), - new SearchAction(dbClient, userSession))); + new GenerateAction(dbClient, system, tokenGenerator, userTokenSupport), + new RevokeAction(dbClient, userTokenSupport), + new SearchAction(dbClient, userTokenSupport))); } @Test diff --git a/tests/src/test/java/org/sonarqube/tests/user/SonarCloudUpdateLoginDuringAuthenticationTest.java b/tests/src/test/java/org/sonarqube/tests/user/SonarCloudUpdateLoginDuringAuthenticationTest.java index 97612a23f3a..6278ab11cbc 100644 --- a/tests/src/test/java/org/sonarqube/tests/user/SonarCloudUpdateLoginDuringAuthenticationTest.java +++ b/tests/src/test/java/org/sonarqube/tests/user/SonarCloudUpdateLoginDuringAuthenticationTest.java @@ -36,8 +36,10 @@ import org.sonarqube.ws.Organizations.Organization; import org.sonarqube.ws.Projects; import org.sonarqube.ws.Qualityprofiles; import org.sonarqube.ws.Settings; +import org.sonarqube.ws.UserTokens; import org.sonarqube.ws.Users; import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.issues.AssignRequest; import org.sonarqube.ws.client.organizations.AddMemberRequest; import org.sonarqube.ws.client.organizations.SearchRequest; @@ -234,6 +236,32 @@ public class SonarCloudUpdateLoginDuringAuthenticationTest { false); } + @Test + public void user_tokens_after_login_update() { + String providerId = tester.users().generateProviderId(); + String oldLogin = tester.users().generateLogin(); + + // First authentication to create the user, then create a ws-client using a token + authenticate(oldLogin, providerId); + String userToken = tester.wsClient().userTokens().generate(new GenerateRequest().setLogin(oldLogin).setName("auth-token")).getToken(); + WsClient userWsClient = tester.as(userToken, null).wsClient(); + + // Generate some user tokens + userWsClient.userTokens().generate(new GenerateRequest().setName("token1")); + userWsClient.userTokens().generate(new GenerateRequest().setName("token2")); + assertThat(userWsClient.userTokens().search(new org.sonarqube.ws.client.usertokens.SearchRequest()).getUserTokensList()) + .extracting(UserTokens.SearchWsResponse.UserToken::getName) + .contains("token1", "token2"); + + // Update login during authentication, check user tokens are still there + String newLogin = tester.users().generateLogin(); + authenticate(newLogin, providerId); + + assertThat(userWsClient.userTokens().search(new org.sonarqube.ws.client.usertokens.SearchRequest()).getUserTokensList()) + .extracting(UserTokens.SearchWsResponse.UserToken::getName) + .contains("token1", "token2"); + } + private void authenticate(String login, String providerId) { tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", login + "," + providerId + ",fake-" + login + ",John,john@email.com"); tester.wsClient().wsConnector().call( diff --git a/tests/src/test/java/org/sonarqube/tests/user/UserSuite.java b/tests/src/test/java/org/sonarqube/tests/user/UserSuite.java index 0f448dd9a7a..e4e1b1c94f8 100644 --- a/tests/src/test/java/org/sonarqube/tests/user/UserSuite.java +++ b/tests/src/test/java/org/sonarqube/tests/user/UserSuite.java @@ -40,7 +40,8 @@ import static util.ItUtils.xooPlugin; NotificationsWsTest.class, OAuth2IdentityProviderTest.class, RootUserInStandaloneModeTest.class, - UsersPageTest.class + UsersPageTest.class, + UserTokensTest.class }) public class UserSuite { diff --git a/tests/src/test/java/org/sonarqube/tests/user/UserTokensTest.java b/tests/src/test/java/org/sonarqube/tests/user/UserTokensTest.java new file mode 100644 index 00000000000..10079a88a18 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/user/UserTokensTest.java @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.user; + +import com.sonar.orchestrator.Orchestrator; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.qa.util.Tester; +import org.sonarqube.ws.UserTokens.SearchWsResponse.UserToken; +import org.sonarqube.ws.Users.CreateWsResponse.User; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.usertokens.GenerateRequest; +import org.sonarqube.ws.client.usertokens.RevokeRequest; +import org.sonarqube.ws.client.usertokens.SearchRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UserTokensTest { + + @ClassRule + public static final Orchestrator orchestrator = UserSuite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Test + public void generate_and_search_for_user_tokens() { + User user = tester.users().generate(); + // Generate tokens for the user + WsClient userWsClient = tester.as(user.getLogin()).wsClient(); + userWsClient.userTokens().generate(new GenerateRequest().setName("token1")); + userWsClient.userTokens().generate(new GenerateRequest().setName("token2")); + // Generate token for another user + User anotherUser = tester.users().generate(); + tester.as(anotherUser.getLogin()).wsClient().userTokens().generate(new GenerateRequest().setName("token")); + + assertThat(userWsClient.userTokens().search(new SearchRequest()).getUserTokensList()) + .extracting(UserToken::getName) + .containsExactlyInAnyOrder("token1", "token2"); + } + + @Test + public void revoke_user_token() { + User user = tester.users().generate(); + WsClient userWsClient = tester.as(user.getLogin()).wsClient(); + userWsClient.userTokens().generate(new GenerateRequest().setName("token1")); + userWsClient.userTokens().generate(new GenerateRequest().setName("token2")); + assertThat(userWsClient.userTokens().search(new SearchRequest()).getUserTokensList()) + .extracting(UserToken::getName) + .containsExactlyInAnyOrder("token1", "token2"); + + userWsClient.userTokens().revoke(new RevokeRequest().setName("token2")); + + assertThat(userWsClient.userTokens().search(new SearchRequest()).getUserTokensList()) + .extracting(UserToken::getName) + .containsExactlyInAnyOrder("token1"); + } + + @Test + public void admin_can_generate_and_search_for_any_user_tokens() { + User user = tester.users().generate(); + User admin = tester.users().generateAdministrator(); + WsClient adminWsClient = tester.as(admin.getLogin()).wsClient(); + + adminWsClient.userTokens().generate(new GenerateRequest().setLogin(user.getLogin()).setName("token1")); + adminWsClient.userTokens().generate(new GenerateRequest().setLogin(user.getLogin()).setName("token2")); + + assertThat(adminWsClient.userTokens().search(new SearchRequest().setLogin(user.getLogin())).getUserTokensList()) + .extracting(UserToken::getName) + .containsExactlyInAnyOrder("token1", "token2"); + } + + @Test + public void admin_can_revoke_token_from_any_user() { + User user = tester.users().generate(); + WsClient userWsClient = tester.as(user.getLogin()).wsClient(); + User admin = tester.users().generateAdministrator(); + WsClient adminWsClient = tester.as(admin.getLogin()).wsClient(); + + userWsClient.userTokens().generate(new GenerateRequest().setName("token1")); + userWsClient.userTokens().generate(new GenerateRequest().setName("token2")); + assertThat(userWsClient.userTokens().search(new SearchRequest()).getUserTokensList()) + .extracting(UserToken::getName) + .containsExactlyInAnyOrder("token1", "token2"); + + adminWsClient.userTokens().revoke(new RevokeRequest().setLogin(user.getLogin()).setName("token2")); + + assertThat(userWsClient.userTokens().search(new SearchRequest()).getUserTokensList()) + .extracting(UserToken::getName) + .containsExactlyInAnyOrder("token1"); + } +}