From 09d8569209aade0e3b4cbd21c6dee9da025b245b Mon Sep 17 00:00:00 2001 From: Belen Pruvost Date: Wed, 14 Jul 2021 17:15:32 +0200 Subject: [PATCH] SONAR-15142 Persisting audits for User operations --- .../org/sonar/auth/github/GitHubSettings.java | 17 +- .../org/sonar/auth/gitlab/GitLabSettings.java | 12 +- .../org/sonar/auth/saml/SamlSettings.java | 24 +-- .../src/main/java/org/sonar/db/DaoModule.java | 2 + .../src/main/java/org/sonar/db/DbClient.java | 7 + .../src/main/java/org/sonar/db/MyBatis.java | 2 + .../java/org/sonar/db/audit/AuditDao.java | 73 ++++++++ .../java/org/sonar/db/audit/AuditDto.java | 89 +++++++++ .../java/org/sonar/db/audit/AuditMapper.java | 40 ++++ .../org/sonar/db/audit/AuditPersister.java | 58 ++++++ .../org/sonar/db/audit/model/NewValue.java | 41 +++++ .../db/audit/model/PropertyNewValue.java | 67 +++++++ .../db/audit/model/UserGroupNewValue.java | 95 ++++++++++ .../sonar/db/audit/model/UserNewValue.java | 133 ++++++++++++++ .../db/audit/model/UserTokenNewValue.java | 83 +++++++++ .../java/org/sonar/db/audit/package-info.java | 23 +++ .../main/java/org/sonar/db/user/GroupDao.java | 26 ++- .../main/java/org/sonar/db/user/UserDao.java | 25 +++ .../java/org/sonar/db/user/UserGroupDao.java | 40 +++- .../org/sonar/db/user/UserPropertiesDao.java | 30 ++- .../java/org/sonar/db/user/UserTokenDao.java | 33 +++- .../org/sonar/db/audit/AuditMapper.xml | 67 +++++++ server/sonar-db-dao/src/schema/schema-sq.ddl | 12 ++ .../java/org/sonar/db/audit/AuditDaoTest.java | 150 +++++++++++++++ .../java/org/sonar/db/user/GroupDaoTest.java | 2 +- .../db/user/GroupDaoWithPersisterTest.java | 107 +++++++++++ .../java/org/sonar/db/user/UserDaoTest.java | 2 +- .../db/user/UserDaoWithPersisterTest.java | 169 +++++++++++++++++ .../org/sonar/db/user/UserGroupDaoTest.java | 14 +- .../user/UserGroupDaoWithPersisterTest.java | 125 +++++++++++++ .../sonar/db/user/UserPropertiesDaoTest.java | 21 ++- .../UserPropertiesDaoWithPersisterTest.java | 173 ++++++++++++++++++ .../org/sonar/db/user/UserTokenDaoTest.java | 4 +- .../user/UserTokenDaoWithPersisterTest.java | 123 +++++++++++++ .../java/org/sonar/db/DbTester.java | 22 ++- .../java/org/sonar/db/audit/AuditTesting.java | 41 +++++ .../java/org/sonar/db/user/UserDbTester.java | 8 +- .../version/v91/CreateAuditTable.java | 73 ++++++++ .../db/migration/version/v91/DbVersion91.java | 3 +- .../version/v91/CreateAuditTableTest.java | 45 +++++ .../version/v91/DbVersion91Test.java | 2 +- .../v91/CreateAuditTableTest/schema.sql | 1 + .../UserLastConnectionDatesUpdaterImpl.java | 4 +- .../authentication/UserRegistrarImpl.java | 5 +- .../org/sonar/server/user/UserUpdater.java | 3 +- ...serLastConnectionDatesUpdaterImplTest.java | 4 +- .../usergroups/DefaultGroupFinderTest.java | 2 +- .../server/user/ws/DeactivateAction.java | 2 +- .../server/user/ws/SetSettingAction.java | 3 +- .../server/usergroups/ws/AddUserAction.java | 2 +- .../server/usergroups/ws/DeleteAction.java | 4 +- .../usergroups/ws/RemoveUserAction.java | 2 +- .../server/usertoken/ws/GenerateAction.java | 2 +- .../server/usertoken/ws/SearchActionTest.java | 2 +- 54 files changed, 2032 insertions(+), 87 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDao.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditMapper.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/NewValue.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/PropertyNewValue.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserGroupNewValue.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserNewValue.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserTokenNewValue.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/audit/package-info.java create mode 100644 server/sonar-db-dao/src/main/resources/org/sonar/db/audit/AuditMapper.xml create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/audit/AuditDaoTest.java create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoWithPersisterTest.java create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoWithPersisterTest.java create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoWithPersisterTest.java create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoWithPersisterTest.java create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoWithPersisterTest.java create mode 100644 server/sonar-db-dao/src/testFixtures/java/org/sonar/db/audit/AuditTesting.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTable.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest.java create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest/schema.sql diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java index 19f2a67693d..56e16beb4fb 100644 --- a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java @@ -34,15 +34,14 @@ import static org.sonar.api.PropertyType.STRING; public class GitHubSettings { - private static final String CLIENT_ID = "sonar.auth.github.clientId.secured"; - private static final String CLIENT_SECRET = "sonar.auth.github.clientSecret.secured"; - private static final String ENABLED = "sonar.auth.github.enabled"; - private static final String ALLOW_USERS_TO_SIGN_UP = "sonar.auth.github.allowUsersToSignUp"; - private static final String GROUPS_SYNC = "sonar.auth.github.groupsSync"; - private static final String API_URL = "sonar.auth.github.apiUrl"; - private static final String WEB_URL = "sonar.auth.github.webUrl"; - - private static final String ORGANIZATIONS = "sonar.auth.github.organizations"; + public static final String CLIENT_ID = "sonar.auth.github.clientId.secured"; + public static final String CLIENT_SECRET = "sonar.auth.github.clientSecret.secured"; + public static final String ENABLED = "sonar.auth.github.enabled"; + public static final String ALLOW_USERS_TO_SIGN_UP = "sonar.auth.github.allowUsersToSignUp"; + public static final String GROUPS_SYNC = "sonar.auth.github.groupsSync"; + public static final String API_URL = "sonar.auth.github.apiUrl"; + public static final String WEB_URL = "sonar.auth.github.webUrl"; + public static final String ORGANIZATIONS = "sonar.auth.github.organizations"; private static final String CATEGORY = CoreProperties.CATEGORY_ALM_INTEGRATION; private static final String SUBCATEGORY = "github"; diff --git a/server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java b/server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java index 69fdb4aa868..c7e6bdc53e1 100644 --- a/server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java +++ b/server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java @@ -32,12 +32,12 @@ import static org.sonar.api.PropertyType.PASSWORD; public class GitLabSettings { - static final String GITLAB_AUTH_ENABLED = "sonar.auth.gitlab.enabled"; - static final String GITLAB_AUTH_URL = "sonar.auth.gitlab.url"; - static final String GITLAB_AUTH_APPLICATION_ID = "sonar.auth.gitlab.applicationId.secured"; - static final String GITLAB_AUTH_SECRET = "sonar.auth.gitlab.secret.secured"; - static final String GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP = "sonar.auth.gitlab.allowUsersToSignUp"; - static final String GITLAB_AUTH_SYNC_USER_GROUPS = "sonar.auth.gitlab.groupsSync"; + public static final String GITLAB_AUTH_ENABLED = "sonar.auth.gitlab.enabled"; + public static final String GITLAB_AUTH_URL = "sonar.auth.gitlab.url"; + public static final String GITLAB_AUTH_APPLICATION_ID = "sonar.auth.gitlab.applicationId.secured"; + public static final String GITLAB_AUTH_SECRET = "sonar.auth.gitlab.secret.secured"; + public static final String GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP = "sonar.auth.gitlab.allowUsersToSignUp"; + public static final String GITLAB_AUTH_SYNC_USER_GROUPS = "sonar.auth.gitlab.groupsSync"; private static final String CATEGORY = CoreProperties.CATEGORY_ALM_INTEGRATION; private static final String SUBCATEGORY = "gitlab"; diff --git a/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java index 4cae9f55b38..7a07e0632e8 100644 --- a/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java +++ b/server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java @@ -33,21 +33,21 @@ import static org.sonar.api.PropertyType.PASSWORD; @ServerSide public class SamlSettings { - private static final String ENABLED = "sonar.auth.saml.enabled"; - private static final String PROVIDER_ID = "sonar.auth.saml.providerId"; - private static final String PROVIDER_NAME = "sonar.auth.saml.providerName"; + public static final String ENABLED = "sonar.auth.saml.enabled"; + public static final String PROVIDER_ID = "sonar.auth.saml.providerId"; + public static final String PROVIDER_NAME = "sonar.auth.saml.providerName"; - private static final String APPLICATION_ID = "sonar.auth.saml.applicationId"; - private static final String LOGIN_URL = "sonar.auth.saml.loginUrl"; - private static final String CERTIFICATE = "sonar.auth.saml.certificate.secured"; + public static final String APPLICATION_ID = "sonar.auth.saml.applicationId"; + public static final String LOGIN_URL = "sonar.auth.saml.loginUrl"; + public static final String CERTIFICATE = "sonar.auth.saml.certificate.secured"; - private static final String USER_LOGIN_ATTRIBUTE = "sonar.auth.saml.user.login"; - private static final String USER_NAME_ATTRIBUTE = "sonar.auth.saml.user.name"; - private static final String USER_EMAIL_ATTRIBUTE = "sonar.auth.saml.user.email"; - private static final String GROUP_NAME_ATTRIBUTE = "sonar.auth.saml.group.name"; + public static final String USER_LOGIN_ATTRIBUTE = "sonar.auth.saml.user.login"; + public static final String USER_NAME_ATTRIBUTE = "sonar.auth.saml.user.name"; + public static final String USER_EMAIL_ATTRIBUTE = "sonar.auth.saml.user.email"; + public static final String GROUP_NAME_ATTRIBUTE = "sonar.auth.saml.group.name"; - private static final String CATEGORY = "security"; - private static final String SUBCATEGORY = "saml"; + public static final String CATEGORY = "security"; + public static final String SUBCATEGORY = "saml"; private final Configuration configuration; diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index 529d9778530..bf6bda78ddb 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -26,6 +26,7 @@ import org.sonar.core.platform.Module; import org.sonar.db.alm.pat.AlmPatDao; import org.sonar.db.alm.setting.AlmSettingDao; import org.sonar.db.alm.setting.ProjectAlmSettingDao; +import org.sonar.db.audit.AuditDao; import org.sonar.db.ce.CeActivityDao; import org.sonar.db.ce.CeQueueDao; import org.sonar.db.ce.CeScannerContextDao; @@ -98,6 +99,7 @@ public class DaoModule extends Module { AnalysisPropertiesDao.class, AuthorizationDao.class, ApplicationProjectsDao.class, + AuditDao.class, BranchDao.class, CeActivityDao.class, CeQueueDao.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index 8b1235d78af..ea6a0516646 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -24,6 +24,7 @@ import java.util.Map; import org.sonar.db.alm.pat.AlmPatDao; import org.sonar.db.alm.setting.AlmSettingDao; import org.sonar.db.alm.setting.ProjectAlmSettingDao; +import org.sonar.db.audit.AuditDao; import org.sonar.db.ce.CeActivityDao; import org.sonar.db.ce.CeQueueDao; import org.sonar.db.ce.CeScannerContextDao; @@ -100,6 +101,7 @@ public class DbClient { private final PropertiesDao propertiesDao; private final AlmSettingDao almSettingDao; private final AlmPatDao almPatDao; + private final AuditDao auditDao; private final ProjectAlmSettingDao projectAlmSettingDao; private final InternalComponentPropertiesDao internalComponentPropertiesDao; private final InternalPropertiesDao internalPropertiesDao; @@ -169,6 +171,7 @@ public class DbClient { map.put(dao.getClass(), dao); } almSettingDao = getDao(map, AlmSettingDao.class); + auditDao = getDao(map, AuditDao.class); almPatDao = getDao(map, AlmPatDao.class); projectAlmSettingDao = getDao(map, ProjectAlmSettingDao.class); schemaMigrationDao = getDao(map, SchemaMigrationDao.class); @@ -255,6 +258,10 @@ public class DbClient { return applicationProjectsDao; } + public AuditDao auditDao() { + return auditDao; + } + public ProjectAlmSettingDao projectAlmSettingDao() { return projectAlmSettingDao; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index e19514ae10a..2f070acc78b 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -37,6 +37,7 @@ import org.sonar.api.Startable; import org.sonar.db.alm.pat.AlmPatMapper; import org.sonar.db.alm.setting.AlmSettingMapper; import org.sonar.db.alm.setting.ProjectAlmSettingMapper; +import org.sonar.db.audit.AuditMapper; import org.sonar.db.ce.CeActivityMapper; import org.sonar.db.ce.CeQueueMapper; import org.sonar.db.ce.CeScannerContextMapper; @@ -223,6 +224,7 @@ public class MyBatis implements Startable { AlmSettingMapper.class, AnalysisPropertiesMapper.class, ApplicationProjectsMapper.class, + AuditMapper.class, AuthorizationMapper.class, BranchMapper.class, CeActivityMapper.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDao.java new file mode 100644 index 00000000000..5279b63575b --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDao.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit; + +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; + +import java.util.List; + +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.MAX_SIZE; + +public class AuditDao implements Dao { + + public static final int DEFAULT_PAGE_SIZE = 10000; + public static final String EXCEEDED_LENGTH = "{ \"valueLengthExceeded\": true }"; + + private final UuidFactory uuidFactory; + private final System2 system2; + + public AuditDao(System2 system2, UuidFactory uuidFactory) { + this.system2 = system2; + this.uuidFactory = uuidFactory; + } + + private static AuditMapper getMapper(DbSession dbSession) { + return dbSession.getMapper(AuditMapper.class); + } + + public List selectAll(DbSession dbSession) { + return getMapper(dbSession).selectAll(); + } + + public List selectByPeriod(DbSession dbSession, long beginning, long end) { + return getMapper(dbSession).selectByPeriod(beginning, end); + } + + public List selectIfBeforeSelectedDate(DbSession dbSession, long end) { + return getMapper(dbSession).selectIfBeforeSelectedDate(end); + } + + public void insert(DbSession dbSession, AuditDto auditDto) { + long now = system2.now(); + auditDto.setUuid(uuidFactory.create()); + auditDto.setCreatedAt(now); + if (auditDto.getNewValue().length() > MAX_SIZE) { + auditDto.setNewValue(EXCEEDED_LENGTH); + } + getMapper(dbSession).insert(auditDto); + } + + public void deleteIfBeforeSelectedDate(DbSession dbSession, long timestamp) { + getMapper(dbSession).deleteIfBeforeSelectedDate(timestamp); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDto.java new file mode 100644 index 00000000000..45460da3603 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDto.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit; + +import javax.annotation.Nullable; + +public class AuditDto { + + private String uuid; + private String userUuid; + private String userLogin; + private String category; + private String operation; + private String newValue; + private long createdAt; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getUserUuid() { + return userUuid; + } + + public void setUserUuid(@Nullable String userUuid) { + this.userUuid = userUuid; + } + + public String getUserLogin() { + return userLogin; + } + + public void setUserLogin(@Nullable String userLogin) { + this.userLogin = userLogin; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public String getNewValue() { + return newValue; + } + + public void setNewValue(String newValue) { + this.newValue = newValue; + } + + public long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(long createdAt) { + this.createdAt = createdAt; + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditMapper.java new file mode 100644 index 00000000000..22c7ea2ef53 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditMapper.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit; + +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface AuditMapper { + + List selectAll(); + + List selectByPeriod(@Param("start") long start, @Param("end") long end); + + List selectIfBeforeSelectedDate(@Param("end") long end); + + void insert(@Param("dto") AuditDto auditDto); + + void delete(@Param("uuids") List uuids); + + void deleteIfBeforeSelectedDate(@Param("timestamp") long timestamp); + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java new file mode 100644 index 00000000000..535f286014c --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit; + +import org.sonar.core.extension.PlatformLevel; +import org.sonar.db.DbSession; +import org.sonar.db.audit.model.NewValue; + +@PlatformLevel(1) +public interface AuditPersister { + + void addUserGroup(DbSession dbSession, NewValue newValue); + + void updateUserGroup(DbSession dbSession, NewValue newValue); + + void deleteUserGroup(DbSession dbSession, NewValue newValue); + + void addUser(DbSession dbSession, NewValue newValue); + + void updateUser(DbSession dbSession, NewValue newValue); + + void deactivateUser(DbSession dbSession, NewValue newValue); + + void addUserToGroup(DbSession dbSession, NewValue newValue); + + void deleteUserFromGroup(DbSession dbSession, NewValue newValue); + + void addUserProperty(DbSession dbSession, NewValue newValue); + + void updateUserProperty(DbSession dbSession, NewValue newValue); + + void deleteUserProperty(DbSession dbSession, NewValue newValue); + + void addUserToken(DbSession dbSession, NewValue newValue); + + void updateUserToken(DbSession dbSession, NewValue newValue); + + void deleteUserToken(DbSession dbSession, NewValue newValue); + + boolean isTrackedProperty(String propertyKey); +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/NewValue.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/NewValue.java new file mode 100644 index 00000000000..b46946cfc47 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/NewValue.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit.model; + +import static com.google.common.base.Strings.isNullOrEmpty; + +public interface NewValue { + + default void addField(StringBuilder sb, String field, String value, boolean isString) { + if (!isNullOrEmpty(value)) { + sb.append(field); + addQuote(sb, isString); + sb.append(value); + addQuote(sb, isString); + sb.append(","); + } + } + + private static void addQuote(StringBuilder sb, boolean isString) { + if(isString) { + sb.append("'"); + } + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/PropertyNewValue.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/PropertyNewValue.java new file mode 100644 index 00000000000..31d19b37e54 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/PropertyNewValue.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit.model; + +import org.sonar.db.user.UserPropertyDto; + +public class PropertyNewValue implements NewValue { + private String propertyKey; + private String propertyValue; + private String userUuid; + private String userLogin; + + public PropertyNewValue(UserPropertyDto userPropertyDto, String login) { + this.propertyKey = userPropertyDto.getKey(); + this.userUuid = userPropertyDto.getUserUuid(); + this.userLogin = login; + + if(!propertyKey.contains(".secured")) { + this.propertyValue = userPropertyDto.getValue(); + } + } + + public String getPropertyKey() { + return this.propertyKey; + } + + public String getPropertyValue() { + return this.propertyValue; + } + + public String getUserUuid() { + return this.userUuid; + } + + public String getUserLogin() { + return this.userLogin; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + addField(sb, "'propertyKey':", this.propertyKey, true); + addField(sb, "'propertyValue':", this.propertyValue, true); + addField(sb, "'userUuid':", this.userUuid, true); + addField(sb, "'userLogin':", this.userLogin, true); + sb.append("}"); + return sb.toString(); + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserGroupNewValue.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserGroupNewValue.java new file mode 100644 index 00000000000..f22892acbdf --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserGroupNewValue.java @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit.model; + +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserGroupDto; + +public class UserGroupNewValue implements NewValue { + private String groupUuid; + private String name; + private String description; + private String userUuid; + private String userLogin; + + public UserGroupNewValue(String groupUuid, String name) { + this.groupUuid = groupUuid; + this.name = name; + } + + public UserGroupNewValue(GroupDto groupDto) { + this.groupUuid = groupDto.getUuid(); + this.name = groupDto.getName(); + this.description = groupDto.getDescription(); + } + + public UserGroupNewValue(GroupDto groupDto, UserDto userDto) { + this.groupUuid = groupDto.getUuid(); + this.name = groupDto.getName(); + this.userUuid = userDto.getUuid(); + this.userLogin = userDto.getLogin(); + } + + public UserGroupNewValue(UserDto userDto) { + this.userUuid = userDto.getUuid(); + this.userLogin = userDto.getLogin(); + } + + public UserGroupNewValue(UserGroupDto userGroupDto, String groupName, String userLogin) { + this.groupUuid = userGroupDto.getGroupUuid(); + this.userUuid = userGroupDto.getUserUuid(); + this.name = groupName; + this.userLogin = userLogin; + } + + public String getGroupUuid() { + return this.groupUuid; + } + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + public String getUserUuid() { + return this.userUuid; + } + + public String getUserLogin() { + return this.userLogin; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + addField(sb, "'groupUuid':", this.groupUuid, true); + addField(sb, "'name':", this.name, true); + addField(sb, "'description':", this.description, true); + addField(sb, "'userUuid':", this.userUuid, true); + addField(sb, "'userLogin':", this.userLogin, true); + sb.append("}"); + return sb.toString(); + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserNewValue.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserNewValue.java new file mode 100644 index 00000000000..5d072fd8bb1 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserNewValue.java @@ -0,0 +1,133 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit.model; + +import org.apache.commons.lang.ObjectUtils; +import org.sonar.db.user.UserDto; + +public class UserNewValue implements NewValue { + private String userUuid; + private String login; + private String name; + private String email; + private Boolean isActive; + private String scmAccounts; + private String externalId; + private String externalLogin; + private String externalIdentityProvider; + private Boolean local; + private Boolean onboarded; + private Boolean root; + private Long lastConnectionDate; + + public UserNewValue(String userUuid, String userLogin) { + this.userUuid = userUuid; + this.login = userLogin; + } + + public UserNewValue(UserDto userDto) { + this.userUuid = userDto.getUuid(); + this.login = userDto.getLogin(); + this.name = userDto.getName(); + this.email = userDto.getEmail(); + this.isActive = userDto.isActive(); + this.scmAccounts = userDto.getScmAccounts(); + this.externalId = userDto.getExternalId(); + this.externalLogin = userDto.getExternalLogin(); + this.externalIdentityProvider = userDto.getExternalIdentityProvider(); + this.local = userDto.isLocal(); + this.onboarded = userDto.isOnboarded(); + this.root = userDto.isRoot(); + this.lastConnectionDate = userDto.getLastConnectionDate(); + } + + public String getUserUuid() { + return this.userUuid; + } + + public String getLogin() { + return this.login; + } + + public String getName() { + return this.name; + } + + public String getEmail() { + return this.email; + } + + public boolean isActive() { + return this.isActive; + } + + public String getScmAccounts() { + return this.scmAccounts; + } + + public String getExternalId() { + return this.externalId; + } + + public String getExternalLogin() { + return this.externalLogin; + } + + public String getExternalIdentityProvider() { + return this.externalIdentityProvider; + } + + public boolean isLocal() { + return this.local; + } + + public boolean isOnboarded() { + return this.onboarded; + } + + public boolean isRoot() { + return this.root; + } + + public Long getLastConnectionDate() { + return this.lastConnectionDate; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + addField(sb, "'userUuid':", this.userUuid, true); + addField(sb, "'login':", this.login, true); + addField(sb, "'name':", this.name, true); + addField(sb, "'email':", this.email, true); + addField(sb, "'isActive':", ObjectUtils.toString(this.isActive), false); + addField(sb, "'scmAccounts':", this.scmAccounts, true); + addField(sb, "'externalId':", this.externalId, true); + addField(sb, "'externalLogin':", this.externalLogin, true); + addField(sb, "'externalIdentityProvider':", this.externalIdentityProvider, true); + addField(sb, "'local':", ObjectUtils.toString(this.local), false); + addField(sb, "'onboarded':", ObjectUtils.toString(this.onboarded), false); + addField(sb, "'root':", ObjectUtils.toString(this.root), false); + addField(sb, "'lastConnectionDate':", ObjectUtils.toString(this.lastConnectionDate), false); + sb.append("}"); + return sb.toString(); + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserTokenNewValue.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserTokenNewValue.java new file mode 100644 index 00000000000..fd12379bdf3 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserTokenNewValue.java @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit.model; + +import org.apache.commons.lang.ObjectUtils; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserTokenDto; + +public class UserTokenNewValue implements NewValue { + private String tokenUuid; + private String userUuid; + private String userLogin; + private String tokenName; + private Long lastConnectionDate; + + public UserTokenNewValue(UserTokenDto userTokenDto, @Nullable String userLogin) { + this.tokenUuid = userTokenDto.getUuid(); + this.tokenName = userTokenDto.getName(); + this.userUuid = userTokenDto.getUserUuid(); + this.lastConnectionDate = userTokenDto.getLastConnectionDate(); + this.userLogin = userLogin; + } + + public UserTokenNewValue(UserDto userDto) { + this.userUuid = userDto.getUuid(); + this.userLogin = userDto.getLogin(); + } + + public UserTokenNewValue(UserDto userDto, String tokenName) { + this(userDto); + this.tokenName = tokenName; + } + + public String getTokenUuid() { + return this.tokenUuid; + } + + public String getUserUuid() { + return this.userUuid; + } + + public String getUserLogin() { + return this.userLogin; + } + + public String getTokenName() { + return this.tokenName; + } + + public Long getLastConnectionDate() { + return this.lastConnectionDate; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + addField(sb, "'tokenUuid':", this.tokenUuid, true); + addField(sb, "'userUuid':", this.userUuid, true); + addField(sb, "'userLogin':", this.userLogin, true); + addField(sb, "'tokenName':", this.tokenName, true); + addField(sb, "'lastConnectionDate':", ObjectUtils.toString(this.lastConnectionDate), false); + sb.append("}"); + return sb.toString(); + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/audit/package-info.java b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/package-info.java new file mode 100644 index 00000000000..1c616daf42d --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/audit/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.db.audit; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/GroupDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/GroupDao.java index a056fba97d6..80b1c7dad4e 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/GroupDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/GroupDao.java @@ -33,20 +33,28 @@ import org.sonar.db.Dao; import org.sonar.db.DaoUtils; import org.sonar.db.DbSession; import org.sonar.db.WildcardPosition; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.UserGroupNewValue; import static org.sonar.db.DatabaseUtils.executeLargeInputs; public class GroupDao implements Dao { private final System2 system; + private AuditPersister auditPersister; public GroupDao(System2 system) { this.system = system; } + public GroupDao(System2 system, AuditPersister auditPersister) { + this(system); + this.auditPersister = auditPersister; + } + /** * @param dbSession - * @param name non-null group name + * @param name non-null group name * @return the group with the given name */ public Optional selectByName(DbSession dbSession, String name) { @@ -66,8 +74,12 @@ public class GroupDao implements Dao { return executeLargeInputs(uuids, mapper(dbSession)::selectByUuids); } - public void deleteByUuid(DbSession dbSession, String groupUuid) { + public void deleteByUuid(DbSession dbSession, String groupUuid, String groupName) { mapper(dbSession).deleteByUuid(groupUuid); + + if (auditPersister != null) { + auditPersister.deleteUserGroup(dbSession, new UserGroupNewValue(groupUuid, groupName)); + } } public int countByQuery(DbSession session, @Nullable String query) { @@ -83,12 +95,22 @@ public class GroupDao implements Dao { item.setCreatedAt(createdAt) .setUpdatedAt(createdAt); mapper(session).insert(item); + + if (auditPersister != null) { + auditPersister.addUserGroup(session, new UserGroupNewValue(item.getUuid(), item.getName())); + } + return item; } public GroupDto update(DbSession session, GroupDto item) { item.setUpdatedAt(new Date(system.now())); mapper(session).update(item); + + if (auditPersister != null) { + auditPersister.updateUserGroup(session, new UserGroupNewValue(item)); + } + return item; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java index 87e77341c71..fda3d03005d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java @@ -34,6 +34,8 @@ import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactory; import org.sonar.db.Dao; import org.sonar.db.DbSession; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.UserNewValue; import org.sonar.db.component.ComponentDto; import org.sonar.db.project.ProjectDto; @@ -48,11 +50,18 @@ public class UserDao implements Dao { private final System2 system2; private final UuidFactory uuidFactory; + private AuditPersister auditPersister; + public UserDao(System2 system2, UuidFactory uuidFactory) { this.system2 = system2; this.uuidFactory = uuidFactory; } + public UserDao(System2 system2, UuidFactory uuidFactory, AuditPersister auditPersister) { + this(system2, uuidFactory); + this.auditPersister = auditPersister; + } + @CheckForNull public UserDto selectByUuid(DbSession session, String uuid) { return mapper(session).selectByUuid(uuid); @@ -105,11 +114,23 @@ public class UserDao implements Dao { public UserDto insert(DbSession session, UserDto dto) { long now = system2.now(); mapper(session).insert(dto.setUuid(uuidFactory.create()).setCreatedAt(now).setUpdatedAt(now)); + + if (auditPersister != null) { + auditPersister.addUser(session, new UserNewValue(dto.getUuid(), dto.getLogin())); + } + return dto; } public UserDto update(DbSession session, UserDto dto) { + return update(session, dto, true); + } + + public UserDto update(DbSession session, UserDto dto, boolean track) { mapper(session).update(dto.setUpdatedAt(system2.now())); + if (track && auditPersister != null) { + auditPersister.updateUser(session, new UserNewValue(dto)); + } return dto; } @@ -123,6 +144,10 @@ public class UserDao implements Dao { public void deactivateUser(DbSession dbSession, UserDto user) { mapper(dbSession).deactivateUser(user.getLogin(), system2.now()); + + if (auditPersister != null) { + auditPersister.deactivateUser(dbSession, new UserNewValue(user.getUuid(), user.getLogin())); + } } public void cleanHomepage(DbSession dbSession, ProjectDto project) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java index d2b589cb4ef..1c1a3cf2a90 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java @@ -22,11 +22,27 @@ package org.sonar.db.user; import java.util.Set; import org.sonar.db.Dao; import org.sonar.db.DbSession; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.UserGroupNewValue; public class UserGroupDao implements Dao { - public UserGroupDto insert(DbSession session, UserGroupDto dto) { + private AuditPersister auditPersister; + + public UserGroupDao() { + } + + public UserGroupDao(AuditPersister auditPersister) { + this.auditPersister = auditPersister; + } + + public UserGroupDto insert(DbSession session, UserGroupDto dto, String groupName, String login) { mapper(session).insert(dto); + + if (auditPersister != null) { + auditPersister.addUserToGroup(session, new UserGroupNewValue(dto, groupName, login)); + } + return dto; } @@ -34,16 +50,28 @@ public class UserGroupDao implements Dao { return mapper(session).selectUserUuidsInGroup(groupUuid); } - public void delete(DbSession session, String groupUuid, String userUuid) { - mapper(session).delete(groupUuid, userUuid); + public void delete(DbSession session, GroupDto group, UserDto user) { + mapper(session).delete(group.getUuid(), user.getUuid()); + + if (auditPersister != null) { + auditPersister.deleteUserFromGroup(session, new UserGroupNewValue(group, user)); + } } - public void deleteByGroupUuid(DbSession session, String groupUuid) { + public void deleteByGroupUuid(DbSession session, String groupUuid, String groupName) { mapper(session).deleteByGroupUuid(groupUuid); + + if (auditPersister != null) { + auditPersister.deleteUserFromGroup(session, new UserGroupNewValue(groupUuid, groupName)); + } } - public void deleteByUserUuid(DbSession dbSession, String userUuid) { - mapper(dbSession).deleteByUserUuid(userUuid); + public void deleteByUserUuid(DbSession dbSession, UserDto userDto) { + mapper(dbSession).deleteByUserUuid(userDto.getUuid()); + + if (auditPersister != null) { + auditPersister.deleteUserFromGroup(dbSession, new UserGroupNewValue(userDto)); + } } private static UserGroupMapper mapper(DbSession session) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java index 753f0f50320..5f1bc0a022d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java @@ -20,35 +20,63 @@ package org.sonar.db.user; import java.util.List; +import javax.annotation.Nullable; import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactory; import org.sonar.db.Dao; import org.sonar.db.DbSession; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.PropertyNewValue; public class UserPropertiesDao implements Dao { private final System2 system2; private final UuidFactory uuidFactory; + private AuditPersister auditPersister; + public UserPropertiesDao(System2 system2, UuidFactory uuidFactory) { this.system2 = system2; this.uuidFactory = uuidFactory; } + public UserPropertiesDao(System2 system2, UuidFactory uuidFactory, AuditPersister auditPersister) { + this(system2, uuidFactory); + this.auditPersister = auditPersister; + } + public List selectByUser(DbSession session, UserDto user) { return mapper(session).selectByUserUuid(user.getUuid()); } - public UserPropertyDto insertOrUpdate(DbSession session, UserPropertyDto dto) { + public UserPropertyDto insertOrUpdate(DbSession session, UserPropertyDto dto, @Nullable String login) { long now = system2.now(); + boolean isUpdate = true; if (mapper(session).update(dto, now) == 0) { mapper(session).insert(dto.setUuid(uuidFactory.create()), now); + isUpdate = false; + } + + if (auditPersister != null && auditPersister.isTrackedProperty(dto.getKey())) { + if (isUpdate) { + auditPersister.updateUserProperty(session, new PropertyNewValue(dto, login)); + } else { + auditPersister.addUserProperty(session, new PropertyNewValue(dto, login)); + } } + return dto; } public void deleteByUser(DbSession session, UserDto user) { + List userProperties = selectByUser(session, user); mapper(session).deleteByUserUuid(user.getUuid()); + + if (auditPersister != null) { + userProperties.stream() + .filter(p -> auditPersister.isTrackedProperty(p.getKey())) + .forEach(p -> auditPersister.deleteUserProperty(session, new PropertyNewValue(p, user.getLogin()))); + } } private static UserPropertiesMapper mapper(DbSession session) { 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 d5cac8104a8..ed4053f92a6 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 @@ -24,9 +24,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.sonar.core.util.UuidFactory; import org.sonar.db.Dao; import org.sonar.db.DbSession; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.UserTokenNewValue; import static org.sonar.core.util.stream.MoreCollectors.toList; import static org.sonar.db.DatabaseUtils.executeLargeInputs; @@ -34,18 +37,36 @@ import static org.sonar.db.DatabaseUtils.executeLargeInputs; public class UserTokenDao implements Dao { private UuidFactory uuidFactory; + private AuditPersister auditPersister; public UserTokenDao(UuidFactory uuidFactory) { this.uuidFactory = uuidFactory; } - public void insert(DbSession dbSession, UserTokenDto userTokenDto) { + public UserTokenDao(UuidFactory uuidFactory, AuditPersister auditPersister) { + this(uuidFactory); + this.auditPersister = auditPersister; + } + + public void insert(DbSession dbSession, UserTokenDto userTokenDto, String userLogin) { userTokenDto.setUuid(uuidFactory.create()); mapper(dbSession).insert(userTokenDto); + + if (auditPersister != null) { + auditPersister.addUserToken(dbSession, new UserTokenNewValue(userTokenDto, userLogin)); + } + } + + public void update(DbSession session, UserTokenDto userTokenDto, @Nullable String userLogin) { + update(session, userTokenDto, true, userLogin); } - public void update(DbSession dbSession, UserTokenDto userTokenDto) { + public void update(DbSession dbSession, UserTokenDto userTokenDto, boolean track, @Nullable String userLogin) { mapper(dbSession).update(userTokenDto); + + if (track && auditPersister != null) { + auditPersister.updateUserToken(dbSession, new UserTokenNewValue(userTokenDto, userLogin)); + } } @CheckForNull @@ -79,10 +100,18 @@ public class UserTokenDao implements Dao { public void deleteByUser(DbSession dbSession, UserDto user) { mapper(dbSession).deleteByUserUuid(user.getUuid()); + + if (auditPersister != null) { + auditPersister.deleteUserToken(dbSession, new UserTokenNewValue(user)); + } } public void deleteByUserAndName(DbSession dbSession, UserDto user, String name) { mapper(dbSession).deleteByUserUuidAndName(user.getUuid(), name); + + if (auditPersister != null) { + auditPersister.deleteUserToken(dbSession, new UserTokenNewValue(user, name)); + } } private static UserTokenMapper mapper(DbSession dbSession) { diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/audit/AuditMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/audit/AuditMapper.xml new file mode 100644 index 00000000000..31ab08d8a71 --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/audit/AuditMapper.xml @@ -0,0 +1,67 @@ + + + + + + + a.uuid as "uuid", + a.user_uuid as "userUuid", + a.user_login as "userLogin", + a.category as "category", + a.operation as "operation", + a.new_value as "newValue", + a.created_at as "createdAt" + + + + + + + + + + INSERT INTO audits + ( + uuid, + user_uuid, + user_login, + category, + operation, + new_value, + created_at + ) + VALUES ( + #{dto.uuid, jdbcType=VARCHAR}, + #{dto.userUuid, jdbcType=VARCHAR}, + #{dto.userLogin, jdbcType=VARCHAR}, + #{dto.category, jdbcType=VARCHAR}, + #{dto.operation, jdbcType=VARCHAR}, + #{dto.newValue, jdbcType=VARCHAR}, + #{dto.createdAt, jdbcType=BIGINT} + ) + + + + delete from audits + where + created_at <= #{timestamp,jdbcType=BIGINT} + + + diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl index 03dd4fd18ce..a2f2267a9bd 100644 --- a/server/sonar-db-dao/src/schema/schema-sq.ddl +++ b/server/sonar-db-dao/src/schema/schema-sq.ddl @@ -100,6 +100,18 @@ CREATE UNIQUE INDEX "UNIQ_APP_PROJECTS" ON "APP_PROJECTS"("APPLICATION_UUID", "P CREATE INDEX "IDX_APP_PROJ_APPLICATION_UUID" ON "APP_PROJECTS"("APPLICATION_UUID"); CREATE INDEX "IDX_APP_PROJ_PROJECT_UUID" ON "APP_PROJECTS"("PROJECT_UUID"); +CREATE TABLE "AUDITS"( + "UUID" VARCHAR(40) NOT NULL, + "USER_UUID" VARCHAR(40) NOT NULL, + "USER_LOGIN" VARCHAR(255) NOT NULL, + "CATEGORY" VARCHAR(20) NOT NULL, + "OPERATION" VARCHAR(50) NOT NULL, + "NEW_VALUE" VARCHAR(4000), + "CREATED_AT" BIGINT NOT NULL +); +ALTER TABLE "AUDITS" ADD CONSTRAINT "PK_AUDITS" PRIMARY KEY("UUID"); +CREATE INDEX "AUDITS_CREATED_AT" ON "AUDITS"("CREATED_AT"); + CREATE TABLE "CE_ACTIVITY"( "UUID" VARCHAR(40) NOT NULL, "TASK_TYPE" VARCHAR(15) NOT NULL, diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/audit/AuditDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/audit/AuditDaoTest.java new file mode 100644 index 00000000000..fcde499416c --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/audit/AuditDaoTest.java @@ -0,0 +1,150 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit; + +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.impl.utils.TestSystem2; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.UuidFactoryImpl; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +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.audit.AuditDao.EXCEEDED_LENGTH; + +public class AuditDaoTest { + + private static final long NOW = 1000000L; + private static final String A_UUID = "SOME_UUID"; + private final TestSystem2 system2 = new TestSystem2().setNow(NOW); + @Rule + public final DbTester db = DbTester.create(system2); + private final DbSession dbSession = db.getSession(); + private final UuidFactory uuidFactory = mock(UuidFactory.class); + + private final AuditDao testAuditDao = new AuditDao(system2, UuidFactoryImpl.INSTANCE); + + @Test + public void selectAll_oneEntryInserted_returnThisEntry() { + AuditDao auditDaoDeterministicUUID = new AuditDao(system2, uuidFactory); + when(uuidFactory.create()).thenReturn(A_UUID); + AuditDto auditDto = AuditTesting.newAuditDto(); + auditDaoDeterministicUUID.insert(dbSession, auditDto); + + List auditDtos = auditDaoDeterministicUUID.selectAll(dbSession); + + assertThat(auditDtos.size()).isEqualTo(1); + assertThat(auditDtos.get(0)) + .extracting(AuditDto::getUuid, AuditDto::getUserLogin, + AuditDto::getUserUuid, AuditDto::getCategory, + AuditDto::getOperation, AuditDto::getNewValue, + AuditDto::getCreatedAt) + .containsExactly(A_UUID, auditDto.getUserLogin(), + auditDto.getUserUuid(), auditDto.getCategory(), + auditDto.getOperation(), auditDto.getNewValue(), + auditDto.getCreatedAt()); + } + + @Test + public void selectAll_100EntriesInserted_100EntriesReturned() { + AuditDao auditDao = new AuditDao(system2, UuidFactoryImpl.INSTANCE); + for(int i=0; i<100; i++) { + AuditDto auditDto = AuditTesting.newAuditDto(); + auditDto.setUuid(randomAlphanumeric(20)); + auditDao.insert(dbSession, auditDto); + } + + List auditDtos = auditDao.selectAll(dbSession); + + assertThat(auditDtos.size()).isEqualTo(100); + } + + @Test + public void selectByPeriod_selectOneRowFromTheMiddle() { + prepareThreeRowsWithDeterministicCreatedAt(); + + List auditDtos = testAuditDao.selectByPeriod(dbSession, 1, 3); + + assertThat(auditDtos.size()).isEqualTo(1); + assertThat(auditDtos.get(0).getCreatedAt()).isEqualTo(2); + } + + @Test + public void selectByPeriod_selectOneRowFromTheEnd() { + prepareThreeRowsWithDeterministicCreatedAt(); + + List auditDtos = testAuditDao.selectByPeriod(dbSession, 2, 4); + + assertThat(auditDtos.size()).isEqualTo(1); + assertThat(auditDtos.get(0).getCreatedAt()).isEqualTo(3); + } + + @Test + public void selectByPeriod_selectAllRows() { + prepareThreeRowsWithDeterministicCreatedAt(); + + List auditDtos = testAuditDao.selectByPeriod(dbSession, 0, 4); + + assertThat(auditDtos.size()).isEqualTo(3); + } + + @Test + public void selectIfBeforeSelectedDate_select1Row() { + prepareThreeRowsWithDeterministicCreatedAt(); + + List auditDtos = testAuditDao.selectIfBeforeSelectedDate(dbSession, 2); + + assertThat(auditDtos.size()).isEqualTo(1); + } + + @Test + public void deleteIfBeforeSelectedDate_deleteTwoRows() { + prepareThreeRowsWithDeterministicCreatedAt(); + + testAuditDao.deleteIfBeforeSelectedDate(dbSession, 2); + + List auditDtos = testAuditDao.selectAll(dbSession); + assertThat(auditDtos.size()).isEqualTo(1); + } + + @Test + public void insert_truncateVeryLongNewValue() { + AuditDto auditDto = AuditTesting.newAuditDto(); + String veryLongString = randomAlphanumeric(5000); + auditDto.setNewValue(veryLongString); + + testAuditDao.insert(dbSession, auditDto); + + assertThat(auditDto.getNewValue()).isEqualTo(EXCEEDED_LENGTH); + } + + private void prepareThreeRowsWithDeterministicCreatedAt() { + for(int i=1; i<=3; i++) { + AuditDto auditDto = AuditTesting.newAuditDto(); + system2.setNow(i); + testAuditDao.insert(dbSession, auditDto); + } + } +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoTest.java index 20b0f4489c8..a7602c17fa3 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoTest.java @@ -214,7 +214,7 @@ public class GroupDaoTest { public void deleteByUuid() { db.getDbClient().groupDao().insert(dbSession, aGroup); - underTest.deleteByUuid(dbSession, aGroup.getUuid()); + underTest.deleteByUuid(dbSession, aGroup.getUuid(), aGroup.getName()); assertThat(db.countRowsOfTable(dbSession, "groups")).isZero(); } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoWithPersisterTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoWithPersisterTest.java new file mode 100644 index 00000000000..1cd59a54b45 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoWithPersisterTest.java @@ -0,0 +1,107 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.user; + +import java.util.Date; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.UserGroupNewValue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class GroupDaoWithPersisterTest { + private static final long NOW = 1_500_000L; + + private final AuditPersister auditPersister = mock(AuditPersister.class); + private final ArgumentCaptor newValueCaptor = ArgumentCaptor.forClass(UserGroupNewValue.class); + + private final System2 system2 = mock(System2.class); + + @Rule + public final DbTester db = DbTester.create(system2, auditPersister); + + private final DbClient dbClient = db.getDbClient(); + private final GroupDao underTest = db.getDbClient().groupDao(); + + private final GroupDto aGroup = new GroupDto() + .setUuid("uuid") + .setName("the-name") + .setDescription("the description"); + + @Before + public void setUp() { + when(system2.now()).thenReturn(NOW); + } + + @Test + public void insert_and_update() { + dbClient.groupDao().insert(db.getSession(), aGroup); + + verify(auditPersister).addUserGroup(eq(db.getSession()), newValueCaptor.capture()); + + UserGroupNewValue newValue = newValueCaptor.getValue(); + + assertThat(newValue) + .extracting(UserGroupNewValue::getGroupUuid, UserGroupNewValue::getName) + .containsExactly(aGroup.getUuid(), aGroup.getName()); + assertThat(newValue.toString()).doesNotContain("'description':"); + + GroupDto dto = new GroupDto() + .setUuid(aGroup.getUuid()) + .setName("new-name") + .setDescription("New description") + .setCreatedAt(new Date(NOW + 1_000L)); + underTest.update(db.getSession(), dto); + + verify(auditPersister).updateUserGroup(eq(db.getSession()), newValueCaptor.capture()); + + newValue = newValueCaptor.getValue(); + assertThat(newValue) + .extracting(UserGroupNewValue::getGroupUuid, UserGroupNewValue::getName, UserGroupNewValue::getDescription) + .containsExactly(dto.getUuid(), dto.getName(), dto.getDescription()); + assertThat(newValue.toString()).contains("'description':"); + } + + @Test + public void deleteByUuid() { + dbClient.groupDao().insert(db.getSession(), aGroup); + + verify(auditPersister).addUserGroup(eq(db.getSession()), any()); + + underTest.deleteByUuid(db.getSession(), aGroup.getUuid(), aGroup.getName()); + + assertThat(db.countRowsOfTable(db.getSession(), "groups")).isZero(); + verify(auditPersister).deleteUserGroup(eq(db.getSession()), newValueCaptor.capture()); + assertThat(newValueCaptor.getValue()) + .extracting(UserGroupNewValue::getGroupUuid, UserGroupNewValue::getName) + .containsExactly(aGroup.getUuid(), aGroup.getName()); + } +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java index 8922f20e22d..596b74f4bc4 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java @@ -785,7 +785,7 @@ public class UserDaoTest { dbClient.groupDao().insert(session, group); UserGroupDto dto = new UserGroupDto().setUserUuid(user.getUuid()).setGroupUuid(group.getUuid()); - dbClient.userGroupDao().insert(session, dto); + dbClient.userGroupDao().insert(session, dto, group.getName(), user.getLogin()); return dto; } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoWithPersisterTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoWithPersisterTest.java new file mode 100644 index 00000000000..541b9b3c07a --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoWithPersisterTest.java @@ -0,0 +1,169 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.user; + +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.sonar.api.impl.utils.TestSystem2; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.UserNewValue; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.sonar.db.user.GroupTesting.newGroupDto; +import static org.sonar.db.user.UserTesting.newUserDto; + +@RunWith(DataProviderRunner.class) +public class UserDaoWithPersisterTest { + private static final long NOW = 1_500_000_000_000L; + private final AuditPersister auditPersister = mock(AuditPersister.class); + + private final TestSystem2 system2 = new TestSystem2().setNow(NOW); + private final ArgumentCaptor newValueCaptor = ArgumentCaptor.forClass(UserNewValue.class); + + @Rule + public final DbTester db = DbTester.create(system2, auditPersister); + + private final DbClient dbClient = db.getDbClient(); + private final UserDao underTest = db.getDbClient().userDao(); + + @Test + public void insert_user_with_default_values() { + UserDto userDto = new UserDto() + .setLogin("john") + .setName("John") + .setEmail("jo@hn.com") + .setExternalLogin("john-1") + .setExternalIdentityProvider("sonarqube") + .setExternalId("EXT_ID"); + underTest.insert(db.getSession(), userDto); + db.getSession().commit(); + UserDto user = underTest.selectActiveUserByLogin(db.getSession(), "john"); + + verify(auditPersister).addUser(eq(db.getSession()), newValueCaptor.capture()); + UserNewValue newValue = newValueCaptor.getValue(); + assertThat(newValue) + .extracting(UserNewValue::getUserUuid, UserNewValue::getLogin) + .containsExactly(user.getUuid(), user.getLogin()); + assertThat(newValue.toString()).doesNotContain("'name':"); + } + + @Test + public void update_user() { + UserDto user = db.users().insertUser(u -> u + .setLogin("john") + .setName("John") + .setEmail("jo@hn.com") + .setActive(true) + .setLocal(true) + .setOnboarded(false) + .setResetPassword(false)); + UserDto updatedUser = newUserDto() + .setUuid(user.getUuid()) + .setLogin("johnDoo") + .setName("John Doo") + .setEmail("jodoo@hn.com") + .setScmAccounts(",jo.hn,john2,johndoo,") + .setActive(false) + .setOnboarded(true) + .setResetPassword(true) + .setSalt("12345") + .setCryptedPassword("abcde") + .setHashMethod("BCRYPT") + .setExternalLogin("johngithub") + .setExternalIdentityProvider("github") + .setExternalId("EXT_ID") + .setLocal(false) + .setHomepageType("project") + .setHomepageParameter("OB1") + .setLastConnectionDate(10_000_000_000L); + underTest.update(db.getSession(), updatedUser); + + verify(auditPersister).updateUser(eq(db.getSession()), newValueCaptor.capture()); + UserNewValue newValue = newValueCaptor.getValue(); + assertThat(newValue) + .extracting(UserNewValue::getUserUuid, UserNewValue::getLogin, UserNewValue::getName, UserNewValue::getEmail, UserNewValue::isActive, + UserNewValue::getScmAccounts, UserNewValue::getExternalId, UserNewValue::getExternalLogin, UserNewValue::getExternalIdentityProvider, + UserNewValue::isLocal, UserNewValue::isOnboarded, UserNewValue::isRoot, UserNewValue::getLastConnectionDate) + .containsExactly(updatedUser.getUuid(), updatedUser.getLogin(), updatedUser.getName(), updatedUser.getEmail(), updatedUser.isActive(), + updatedUser.getScmAccounts(), updatedUser.getExternalId(), updatedUser.getExternalLogin(), updatedUser.getExternalIdentityProvider(), + updatedUser.isLocal(), updatedUser.isOnboarded(), updatedUser.isRoot(), updatedUser.getLastConnectionDate()); + assertThat(newValue.toString()).contains("'name':"); + } + + @Test + public void update_user_without_track() { + UserDto user = db.users().insertUser(u -> u + .setLogin("john") + .setName("John") + .setEmail("jo@hn.com") + .setActive(true) + .setLocal(true) + .setOnboarded(false) + .setResetPassword(false)); + + verify(auditPersister).addUser(eq(db.getSession()), newValueCaptor.capture()); + + UserDto updatedUser = newUserDto() + .setUuid(user.getUuid()) + .setLogin("johnDoo"); + underTest.update(db.getSession(), updatedUser, false); + + verifyNoMoreInteractions(auditPersister); + } + + @Test + public void deactivate_user() { + UserDto user = insertActiveUser(); + insertUserGroup(user); + underTest.update(db.getSession(), user.setLastConnectionDate(10_000_000_000L)); + db.getSession().commit(); + underTest.deactivateUser(db.getSession(), user); + + verify(auditPersister).deactivateUser(eq(db.getSession()), newValueCaptor.capture()); + assertThat(newValueCaptor.getValue()) + .extracting(UserNewValue::getUserUuid, UserNewValue::getLogin) + .containsExactly(user.getUuid(), user.getLogin()); + } + + private UserDto insertActiveUser() { + UserDto dto = newUserDto().setActive(true); + underTest.insert(db.getSession(), dto); + return dto; + } + + private UserGroupDto insertUserGroup(UserDto user) { + GroupDto group = newGroupDto().setName(randomAlphanumeric(30)); + dbClient.groupDao().insert(db.getSession(), group); + + UserGroupDto dto = new UserGroupDto().setUserUuid(user.getUuid()).setGroupUuid(group.getUuid()); + dbClient.userGroupDao().insert(db.getSession(), dto, group.getName(), user.getLogin()); + return dto; + } +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoTest.java index 690e11a40e4..2daf599a56c 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoTest.java @@ -42,7 +42,7 @@ public class UserGroupDaoTest { GroupDto group = dbTester.users().insertGroup(); UserGroupDto userGroupDto = new UserGroupDto().setUserUuid(user.getUuid()).setGroupUuid(group.getUuid()); - underTest.insert(dbTester.getSession(), userGroupDto); + underTest.insert(dbTester.getSession(), userGroupDto, group.getName(), user.getLogin()); dbTester.getSession().commit(); assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(dbTester.getSession(), user.getUuid())).containsOnly(group.getUuid()); @@ -58,9 +58,9 @@ public class UserGroupDaoTest { UserGroupDto userGroupDto1 = new UserGroupDto().setUserUuid(user1.getUuid()).setGroupUuid(group1.getUuid()); UserGroupDto userGroupDto2 = new UserGroupDto().setUserUuid(user2.getUuid()).setGroupUuid(group2.getUuid()); UserGroupDto userGroupDto3 = new UserGroupDto().setUserUuid(user3.getUuid()).setGroupUuid(group2.getUuid()); - underTest.insert(dbSession, userGroupDto1); - underTest.insert(dbSession, userGroupDto2); - underTest.insert(dbSession, userGroupDto3); + underTest.insert(dbSession, userGroupDto1, group1.getName(), user1.getLogin()); + underTest.insert(dbSession, userGroupDto2, group2.getName(), user2.getLogin()); + underTest.insert(dbSession, userGroupDto3, group2.getName(), user3.getLogin()); dbTester.getSession().commit(); Set userUuids = underTest.selectUserUuidsInGroup(dbTester.getSession(), group2.getUuid()); @@ -74,7 +74,7 @@ public class UserGroupDaoTest { GroupDto group1 = dbTester.users().insertGroup(); GroupDto group2 = dbTester.users().insertGroup(); UserGroupDto userGroupDto1 = new UserGroupDto().setUserUuid(user1.getUuid()).setGroupUuid(group1.getUuid()); - underTest.insert(dbSession, userGroupDto1); + underTest.insert(dbSession, userGroupDto1, group1.getName(), user1.getLogin()); dbTester.getSession().commit(); Set userUuids = underTest.selectUserUuidsInGroup(dbTester.getSession(), group2.getUuid()); @@ -93,7 +93,7 @@ public class UserGroupDaoTest { dbTester.users().insertMember(group2, user1); dbTester.users().insertMember(group2, user2); - underTest.deleteByGroupUuid(dbTester.getSession(), group1.getUuid()); + underTest.deleteByGroupUuid(dbTester.getSession(), group1.getUuid(), group1.getName()); dbTester.getSession().commit(); assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(dbTester.getSession(), user1.getUuid())).containsOnly(group2.getUuid()); @@ -111,7 +111,7 @@ public class UserGroupDaoTest { dbTester.users().insertMember(group2, user1); dbTester.users().insertMember(group2, user2); - underTest.deleteByUserUuid(dbTester.getSession(), user1.getUuid()); + underTest.deleteByUserUuid(dbTester.getSession(), user1); dbTester.getSession().commit(); assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(dbTester.getSession(), user1.getUuid())).isEmpty(); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoWithPersisterTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoWithPersisterTest.java new file mode 100644 index 00000000000..942de92882d --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoWithPersisterTest.java @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.user; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.UserGroupNewValue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class UserGroupDaoWithPersisterTest { + private final AuditPersister auditPersister = mock(AuditPersister.class); + + @Rule + public final DbTester db = DbTester.create(System2.INSTANCE, auditPersister); + + private final ArgumentCaptor newValueCaptor = ArgumentCaptor.forClass(UserGroupNewValue.class); + + private final DbClient dbClient = db.getDbClient(); + private final UserGroupDao underTest = dbClient.userGroupDao(); + + @Test + public void insert() { + UserDto user = db.users().insertUser(); + + verify(auditPersister).addUser(eq(db.getSession()), any()); + + GroupDto group = db.users().insertGroup(); + UserGroupDto userGroupDto = new UserGroupDto().setUserUuid(user.getUuid()).setGroupUuid(group.getUuid()); + underTest.insert(db.getSession(), userGroupDto, group.getName(), user.getLogin()); + db.getSession().commit(); + + verify(auditPersister).addUserToGroup(eq(db.getSession()), newValueCaptor.capture()); + assertThat(db.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(db.getSession(), user.getUuid())).containsOnly(group.getUuid()); + assertThat(newValueCaptor.getValue()) + .extracting(UserGroupNewValue::getGroupUuid, UserGroupNewValue::getName, UserGroupNewValue::getUserUuid, UserGroupNewValue::getUserLogin) + .containsExactly(group.getUuid(), group.getName(), user.getUuid(), user.getLogin()); + } + + @Test + public void delete_members_by_group_uuid() { + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + GroupDto group1 = db.users().insertGroup(); + GroupDto group2 = db.users().insertGroup(); + db.users().insertMember(group1, user1); + db.users().insertMember(group1, user2); + db.users().insertMember(group2, user1); + db.users().insertMember(group2, user2); + underTest.deleteByGroupUuid(db.getSession(), group1.getUuid(), group1.getName()); + db.getSession().commit(); + + assertThat(db.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(db.getSession(), user1.getUuid())).containsOnly(group2.getUuid()); + assertThat(db.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(db.getSession(), user2.getUuid())).containsOnly(group2.getUuid()); + verify(auditPersister).deleteUserFromGroup(eq(db.getSession()), newValueCaptor.capture()); + assertThat(newValueCaptor.getValue()) + .extracting(UserGroupNewValue::getGroupUuid, UserGroupNewValue::getName) + .containsExactly(group1.getUuid(), group1.getName()); + } + + @Test + public void delete_by_user() { + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + GroupDto group1 = db.users().insertGroup(); + GroupDto group2 = db.users().insertGroup(); + db.users().insertMember(group1, user1); + db.users().insertMember(group1, user2); + db.users().insertMember(group2, user1); + db.users().insertMember(group2, user2); + underTest.deleteByUserUuid(db.getSession(), user1); + db.getSession().commit(); + + assertThat(db.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(db.getSession(), user1.getUuid())).isEmpty(); + assertThat(db.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(db.getSession(), user2.getUuid())).containsOnly(group1.getUuid(), group2.getUuid()); + verify(auditPersister).deleteUserFromGroup(eq(db.getSession()), newValueCaptor.capture()); + assertThat(newValueCaptor.getValue()) + .extracting(UserGroupNewValue::getUserUuid, UserGroupNewValue::getUserLogin) + .containsExactly(user1.getUuid(), user1.getLogin()); + } + + @Test + public void delete_by_user_and_group() { + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + GroupDto group1 = db.users().insertGroup(); + db.users().insertMember(group1, user1); + db.users().insertMember(group1, user2); + underTest.delete(db.getSession(), group1, user1); + db.getSession().commit(); + + assertThat(db.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(db.getSession(), user1.getUuid())).isEmpty(); + assertThat(db.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(db.getSession(), user2.getUuid())).containsOnly(group1.getUuid()); + verify(auditPersister).deleteUserFromGroup(eq(db.getSession()), newValueCaptor.capture()); + assertThat(newValueCaptor.getValue()) + .extracting(UserGroupNewValue::getGroupUuid, UserGroupNewValue::getName, UserGroupNewValue::getUserUuid, UserGroupNewValue::getUserLogin) + .containsExactly(group1.getUuid(), group1.getName(), user1.getUuid(), user1.getLogin()); + } +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java index 9ebc94e8fc5..1fa36fbd193 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java @@ -61,9 +61,10 @@ public class UserPropertiesDaoTest { UserDto user = db.users().insertUser(); UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() - .setUserUuid(user.getUuid()) - .setKey("a_key") - .setValue("a_value")); + .setUserUuid(user.getUuid()) + .setKey("a_key") + .setValue("a_value"), + user.getLogin()); Map map = db.selectFirst(db.getSession(), "select uuid as \"uuid\",\n" + " user_uuid as \"userUuid\",\n" + @@ -85,15 +86,17 @@ public class UserPropertiesDaoTest { public void update() { UserDto user = db.users().insertUser(); UserPropertyDto userProperty = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() - .setUserUuid(user.getUuid()) - .setKey("a_key") - .setValue("old_value")); + .setUserUuid(user.getUuid()) + .setKey("a_key") + .setValue("old_value"), + user.getLogin()); system2.setNow(2_000_000_000_000L); underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() - .setUserUuid(user.getUuid()) - .setKey("a_key") - .setValue("new_value")); + .setUserUuid(user.getUuid()) + .setKey("a_key") + .setValue("new_value"), + user.getLogin()); Map map = db.selectFirst(db.getSession(), "select uuid as \"uuid\",\n" + " user_uuid as \"userUuid\",\n" + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoWithPersisterTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoWithPersisterTest.java new file mode 100644 index 00000000000..fbf81f564a6 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoWithPersisterTest.java @@ -0,0 +1,173 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.user; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.impl.utils.TestSystem2; +import org.sonar.db.DbTester; +import org.sonar.db.audit.AuditPersister; +import org.sonar.db.audit.model.PropertyNewValue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class UserPropertiesDaoWithPersisterTest { + private static final long NOW = 1_500_000_000_000L; + private static final String SECURED_PROPERTY_KEY = "a_key.secured"; + private static final String PROPERTY_KEY = "a_key"; + + private final AuditPersister auditPersister = mock(AuditPersister.class); + private ArgumentCaptor newValueCaptor = ArgumentCaptor.forClass(PropertyNewValue.class); + + private TestSystem2 system2 = new TestSystem2().setNow(NOW); + + @Rule + public DbTester db = DbTester.create(system2, auditPersister); + + private UserPropertiesDao underTest = db.getDbClient().userPropertiesDao(); + + @Test + public void insert_tracked_property() { + when(auditPersister.isTrackedProperty(PROPERTY_KEY)).thenReturn(true); + + UserDto user = db.users().insertUser(); + + verify(auditPersister).addUser(eq(db.getSession()), any()); + + UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey(PROPERTY_KEY) + .setValue("a_value"), + user.getLogin()); + + verify(auditPersister).addUserProperty(eq(db.getSession()), newValueCaptor.capture()); + verify(auditPersister).isTrackedProperty(PROPERTY_KEY); + assertThat(newValueCaptor.getValue()) + .extracting(PropertyNewValue::getPropertyKey, PropertyNewValue::getPropertyValue, PropertyNewValue::getUserUuid, + PropertyNewValue::getUserLogin) + .containsExactly(userSetting.getKey(), userSetting.getValue(), user.getUuid(), user.getLogin()); + + } + + @Test + public void insert_tracked_secured_property() { + when(auditPersister.isTrackedProperty(SECURED_PROPERTY_KEY)).thenReturn(true); + + UserDto user = db.users().insertUser(); + + verify(auditPersister).addUser(eq(db.getSession()), any()); + + UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey(SECURED_PROPERTY_KEY) + .setValue("a_value"), + user.getLogin()); + + verify(auditPersister).isTrackedProperty(SECURED_PROPERTY_KEY); + verify(auditPersister).addUserProperty(eq(db.getSession()), newValueCaptor.capture()); + PropertyNewValue newValue = newValueCaptor.getValue(); + assertThat(newValue) + .extracting(PropertyNewValue::getPropertyKey, PropertyNewValue::getPropertyValue, PropertyNewValue::getUserUuid, + PropertyNewValue::getUserLogin) + .containsExactly(userSetting.getKey(), null, user.getUuid(), user.getLogin()); + assertThat(newValue.toString()).doesNotContain("'propertyValue':"); + } + + @Test + public void insert_not_tracked_property() { + when(auditPersister.isTrackedProperty(PROPERTY_KEY)).thenReturn(false); + + UserDto user = db.users().insertUser(); + underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey(PROPERTY_KEY) + .setValue("a_value"), + user.getLogin()); + + verify(auditPersister).addUser(eq(db.getSession()), any()); + verify(auditPersister).isTrackedProperty(PROPERTY_KEY); + verifyNoMoreInteractions(auditPersister); + } + + @Test + public void update() { + when(auditPersister.isTrackedProperty(PROPERTY_KEY)).thenReturn(true); + + UserDto user = db.users().insertUser(); + underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey(PROPERTY_KEY) + .setValue("old_value"), + user.getLogin()); + system2.setNow(2_000_000_000_000L); + UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey(PROPERTY_KEY) + .setValue("new_value"), + user.getLogin()); + + verify(auditPersister).addUser(eq(db.getSession()), any()); + verify(auditPersister).addUserProperty(eq(db.getSession()), any()); + verify(auditPersister).updateUserProperty(eq(db.getSession()), newValueCaptor.capture()); + assertThat(newValueCaptor.getValue()) + .extracting(PropertyNewValue::getPropertyKey, PropertyNewValue::getPropertyValue, PropertyNewValue::getUserUuid, + PropertyNewValue::getUserLogin) + .containsExactly(userSetting.getKey(), userSetting.getValue(), user.getUuid(), user.getLogin()); + } + + @Test + public void delete_by_user() { + when(auditPersister.isTrackedProperty(PROPERTY_KEY)).thenReturn(true); + when(auditPersister.isTrackedProperty(SECURED_PROPERTY_KEY)).thenReturn(false); + + UserDto user = db.users().insertUser(); + UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey(PROPERTY_KEY) + .setValue("a_value"), + user.getLogin()); + underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey(SECURED_PROPERTY_KEY) + .setValue("another_value"), + user.getLogin()); + underTest.deleteByUser(db.getSession(), user); + + verify(auditPersister).addUser(eq(db.getSession()), any()); + verify(auditPersister).addUserProperty(eq(db.getSession()), any()); + verify(auditPersister).addUserProperty(eq(db.getSession()), any()); + verify(auditPersister, times(2)).isTrackedProperty(PROPERTY_KEY); + verify(auditPersister, times(2)).isTrackedProperty(SECURED_PROPERTY_KEY); + verify(auditPersister).deleteUserProperty(eq(db.getSession()), newValueCaptor.capture()); + verifyNoMoreInteractions(auditPersister); + assertThat(newValueCaptor.getValue()) + .extracting(PropertyNewValue::getPropertyKey, PropertyNewValue::getPropertyValue, PropertyNewValue::getUserUuid, + PropertyNewValue::getUserLogin) + .containsExactly(userSetting.getKey(), userSetting.getValue(), user.getUuid(), user.getLogin()); + } +} 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 b56bc154f8e..e258b5e55ca 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 @@ -45,7 +45,7 @@ public class UserTokenDaoTest { public void insert_token() { UserTokenDto userToken = newUserToken(); - underTest.insert(db.getSession(), userToken); + underTest.insert(db.getSession(), userToken, "login"); UserTokenDto userTokenFromDb = underTest.selectByTokenHash(db.getSession(), userToken.getTokenHash()); assertThat(userTokenFromDb).isNotNull(); @@ -63,7 +63,7 @@ public class UserTokenDaoTest { UserTokenDto userToken2 = db.users().insertToken(user1); assertThat(underTest.selectByTokenHash(dbSession, userToken1.getTokenHash()).getLastConnectionDate()).isNull(); - underTest.update(dbSession, userToken1.setLastConnectionDate(10_000_000_000L)); + underTest.update(dbSession, userToken1.setLastConnectionDate(10_000_000_000L), false, null); UserTokenDto userTokenReloaded = underTest.selectByTokenHash(dbSession, userToken1.getTokenHash()); assertThat(userTokenReloaded.getLastConnectionDate()).isEqualTo(10_000_000_000L); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoWithPersisterTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoWithPersisterTest.java new file mode 100644 index 00000000000..6e8df27c40e --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoWithPersisterTest.java @@ -0,0 +1,123 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.user; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +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.audit.AuditPersister; +import org.sonar.db.audit.model.UserTokenNewValue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.sonar.db.user.UserTokenTesting.newUserToken; + +public class UserTokenDaoWithPersisterTest { + private final AuditPersister auditPersister = mock(AuditPersister.class); + private final ArgumentCaptor newValueCaptor = ArgumentCaptor.forClass(UserTokenNewValue.class); + + @Rule + public final DbTester db = DbTester.create(System2.INSTANCE, auditPersister); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private final DbSession dbSession = db.getSession(); + private final DbClient dbClient = db.getDbClient(); + private final UserTokenDao underTest = dbClient.userTokenDao(); + + @Test + public void insert_token() { + UserTokenDto userToken = newUserToken(); + underTest.insert(db.getSession(), userToken, "login"); + + verify(auditPersister).addUserToken(eq(db.getSession()), newValueCaptor.capture()); + UserTokenDto userTokenFromDb = underTest.selectByTokenHash(db.getSession(), userToken.getTokenHash()); + assertThat(userTokenFromDb).isNotNull(); + assertThat(userTokenFromDb.getUuid()).isEqualTo(userToken.getUuid()); + assertThat(userTokenFromDb.getName()).isEqualTo(userToken.getName()); + assertThat(userTokenFromDb.getCreatedAt()).isEqualTo(userToken.getCreatedAt()); + assertThat(userTokenFromDb.getTokenHash()).isEqualTo(userToken.getTokenHash()); + assertThat(userTokenFromDb.getUserUuid()).isEqualTo(userToken.getUserUuid()); + UserTokenNewValue newValue = newValueCaptor.getValue(); + assertThat(newValue) + .extracting(UserTokenNewValue::getTokenUuid, UserTokenNewValue::getTokenName, UserTokenNewValue::getUserUuid, UserTokenNewValue::getLastConnectionDate) + .containsExactly(userToken.getUuid(), userToken.getName(), userToken.getUserUuid(), userToken.getLastConnectionDate()); + assertThat(newValue.toString()).contains("'tokenUuid':"); + } + + @Test + public void update_token() { + UserDto user1 = db.users().insertUser(); + UserTokenDto userToken1 = db.users().insertToken(user1); + + assertThat(underTest.selectByTokenHash(dbSession, userToken1.getTokenHash()).getLastConnectionDate()).isNull(); + + underTest.update(dbSession, userToken1.setLastConnectionDate(10_000_000_000L), false, null); + underTest.update(dbSession, userToken1.setName("new_name"), user1.getLogin()); + + verify(auditPersister).updateUserToken(eq(db.getSession()), newValueCaptor.capture()); + assertThat(newValueCaptor.getValue()) + .extracting(UserTokenNewValue::getTokenUuid, UserTokenNewValue::getTokenName, UserTokenNewValue::getUserUuid, UserTokenNewValue::getLastConnectionDate) + .containsExactly(userToken1.getUuid(), "new_name", userToken1.getUserUuid(), userToken1.getLastConnectionDate()); + } + + @Test + 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.selectByUser(dbSession, user1)).isEmpty(); + assertThat(underTest.selectByUser(dbSession, user2)).hasSize(1); + verify(auditPersister).deleteUserToken(eq(db.getSession()), newValueCaptor.capture()); + assertThat(newValueCaptor.getValue()) + .extracting(UserTokenNewValue::getUserUuid, UserTokenNewValue::getUserLogin) + .containsExactly(user1.getUuid(), user1.getLogin()); + } + + @Test + 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(); + verify(auditPersister).deleteUserToken(eq(db.getSession()), newValueCaptor.capture()); + assertThat(newValueCaptor.getValue()) + .extracting(UserTokenNewValue::getUserUuid, UserTokenNewValue::getUserLogin, UserTokenNewValue::getTokenName) + .containsExactly(user1.getUuid(), user1.getLogin(), "name"); + } +} diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java index c71125da615..8d795b2411c 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java @@ -34,6 +34,7 @@ import org.sonar.core.util.SequenceUuidFactory; import org.sonar.core.util.UuidFactory; import org.sonar.db.alm.integration.pat.AlmPatsDbTester; import org.sonar.db.almsettings.AlmSettingsDbTester; +import org.sonar.db.audit.AuditPersister; import org.sonar.db.component.ComponentDbTester; import org.sonar.db.component.ProjectLinkDbTester; import org.sonar.db.event.EventDbTester; @@ -88,11 +89,11 @@ public class DbTester extends AbstractDbTester { private final AlmSettingsDbTester almSettingsDbTester; private final AlmPatsDbTester almPatsDbtester; - private DbTester(System2 system2, @Nullable String schemaPath, MyBatisConfExtension... confExtensions) { + private DbTester(System2 system2, @Nullable String schemaPath, AuditPersister auditPersister, MyBatisConfExtension... confExtensions) { super(TestDbImpl.create(schemaPath, confExtensions)); this.system2 = system2; - initDbClient(); + initDbClient(auditPersister); this.userTester = new UserDbTester(this); this.componentTester = new ComponentDbTester(this); this.componentLinkTester = new ProjectLinkDbTester(this); @@ -118,19 +119,26 @@ public class DbTester extends AbstractDbTester { } public static DbTester create() { - return new DbTester(System2.INSTANCE, null); + return new DbTester(System2.INSTANCE, null, null); + } + + public static DbTester create(System2 system2, AuditPersister auditPersister) { + return new DbTester(system2, null, auditPersister); } public static DbTester create(System2 system2) { - return new DbTester(system2, null); + return new DbTester(system2, null, null); } public static DbTester createWithExtensionMappers(System2 system2, Class firstMapperClass, Class... otherMapperClasses) { - return new DbTester(system2, null, new DbTesterMyBatisConfExtension(firstMapperClass, otherMapperClasses)); + return new DbTester(system2, null, null, new DbTesterMyBatisConfExtension(firstMapperClass, otherMapperClasses)); } - private void initDbClient() { + private void initDbClient(AuditPersister auditPersister) { TransientPicoContainer ioc = new TransientPicoContainer(); + if (auditPersister != null) { + ioc.addComponent(auditPersister); + } ioc.addComponent(db.getMyBatis()); ioc.addComponent(system2); ioc.addComponent(uuidFactory); @@ -145,7 +153,7 @@ public class DbTester extends AbstractDbTester { protected void before() { db.start(); db.truncateTables(); - initDbClient(); + initDbClient(null); } public UserDbTester users() { diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/audit/AuditTesting.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/audit/AuditTesting.java new file mode 100644 index 00000000000..094ae46db49 --- /dev/null +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/audit/AuditTesting.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.db.audit; + +import java.util.Random; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; + +public class AuditTesting { + + private static final Random random = new Random(); + + public static AuditDto newAuditDto() { + AuditDto auditDto = new AuditDto(); + auditDto.setUuid(randomAlphanumeric(20)); + auditDto.setUserUuid(randomAlphanumeric(40)); + auditDto.setUserLogin(randomAlphanumeric(40)); + auditDto.setNewValue(randomAlphanumeric(2000)); + auditDto.setOperation("operation"); + auditDto.setCategory("category"); + auditDto.setCreatedAt(random.nextLong()); + return auditDto; + } +} diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java index 73bc44da23c..ac863cf91c8 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java @@ -134,7 +134,7 @@ public class UserDbTester { public final UserPropertyDto insertUserSetting(UserDto user, Consumer... populators) { UserPropertyDto dto = UserTesting.newUserSettingDto(user); stream(populators).forEach(p -> p.accept(dto)); - dbClient.userPropertiesDao().insertOrUpdate(db.getSession(), dto); + dbClient.userPropertiesDao().insertOrUpdate(db.getSession(), dto, user.getLogin()); db.commit(); return dto; } @@ -176,7 +176,7 @@ public class UserDbTester { public UserGroupDto insertMember(GroupDto group, UserDto user) { UserGroupDto dto = new UserGroupDto().setGroupUuid(group.getUuid()).setUserUuid(user.getUuid()); - db.getDbClient().userGroupDao().insert(db.getSession(), dto); + db.getDbClient().userGroupDao().insert(db.getSession(), dto, group.getName(), user.getLogin()); db.commit(); return dto; } @@ -184,7 +184,7 @@ public class UserDbTester { public void insertMembers(GroupDto group, UserDto... users) { Arrays.stream(users).forEach(user -> { UserGroupDto dto = new UserGroupDto().setGroupUuid(group.getUuid()).setUserUuid(user.getUuid()); - db.getDbClient().userGroupDao().insert(db.getSession(), dto); + db.getDbClient().userGroupDao().insert(db.getSession(), dto, group.getName(), user.getLogin()); }); db.commit(); } @@ -342,7 +342,7 @@ public class UserDbTester { public final UserTokenDto insertToken(UserDto user, Consumer... populators) { UserTokenDto dto = UserTokenTesting.newUserToken().setUserUuid(user.getUuid()); stream(populators).forEach(p -> p.accept(dto)); - db.getDbClient().userTokenDao().insert(db.getSession(), dto); + db.getDbClient().userTokenDao().insert(db.getSession(), dto, user.getLogin()); db.commit(); return dto; } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTable.java new file mode 100644 index 00000000000..b7dd62f460c --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTable.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.v91; + +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef; +import org.sonar.server.platform.db.migration.def.ColumnDef; +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.CreateTableBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static java.util.Arrays.stream; +import static java.util.stream.Stream.concat; +import static java.util.stream.Stream.of; +import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; +import java.sql.SQLException; + +public class CreateAuditTable extends DdlChange { + + public CreateAuditTable(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + BigIntegerColumnDef createdAtColumn = newBigIntegerColumnDefBuilder().setColumnName("created_at").setIsNullable(false).build(); + var tableName = "audits"; + context.execute(new CreateTableBuilder(getDialect(), tableName) + .addPkColumn(newVarcharColumnDefBuilder().setColumnName("uuid").setIsNullable(false).setLimit(UUID_SIZE).build()) + .addColumn(newVarcharColumnBuilder("user_uuid").setIsNullable(false).setLimit(UUID_SIZE).build()) + .addColumn(newVarcharColumnBuilder("user_login").setIsNullable(false).setLimit(255).build()) + .addColumn(newVarcharColumnBuilder("category").setIsNullable(false).setLimit(20).build()) + .addColumn(newVarcharColumnBuilder("operation").setIsNullable(false).setLimit(50).build()) + .addColumn(newVarcharColumnBuilder("new_value").setIsNullable(true).setLimit(4000).build()) + .addColumn(createdAtColumn) + .build()); + + addIndex(context, tableName, "audits_created_at", false, createdAtColumn); + } + + private static void addIndex(Context context, String table, String index, boolean unique, ColumnDef firstColumn, ColumnDef... otherColumns) { + CreateIndexBuilder builder = new CreateIndexBuilder() + .setTable(table) + .setName(index) + .setUnique(unique); + concat(of(firstColumn), stream(otherColumns)).forEach(builder::addColumn); + context.execute(builder.build()); + } + + private static VarcharColumnDef.Builder newVarcharColumnBuilder(String column) { + return newVarcharColumnDefBuilder().setColumnName(column); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java index 00be71d3e32..ab24898999f 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java @@ -31,6 +31,7 @@ public class DbVersion91 implements DbVersion { .add(6003, "Drop custom metrics data from 'live_measures' table", DropCustomMetricsLiveMeasuresData.class) .add(6004, "Drop custom metrics data from 'project_measures' table", DropCustomMetricsProjectMeasuresData.class) .add(6005, "Drop custom metrics data from 'metrics' table", DropUserManagedMetricsData.class) - .add(6006, "Drop 'user_managed' column from 'metrics' table", DropUserManagedColumnFromMetricsTable.class); + .add(6006, "Drop 'user_managed' column from 'metrics' table", DropUserManagedColumnFromMetricsTable.class) + .add(6007, "Create Audit table", CreateAuditTable.class); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest.java new file mode 100644 index 00000000000..c1be0fdbd83 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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.v91; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +import java.sql.SQLException; + +public class CreateAuditTableTest { + private static final String TABLE_NAME = "audits"; + + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(CreateAuditTableTest.class, "schema.sql"); + + private final CreateAuditTable underTest = new CreateAuditTable(db.database()); + + @Test + public void migration_should_create_a_table_with_index() throws SQLException { + db.assertTableDoesNotExist(TABLE_NAME); + + underTest.execute(); + + db.assertTableExists(TABLE_NAME); + db.assertIndex(TABLE_NAME, "audits_created_at", "created_at"); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java index bab346ea2b5..0e05e1332e1 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java @@ -41,7 +41,7 @@ public class DbVersion91Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 6); + verifyMigrationCount(underTest, 7); } } diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest/schema.sql new file mode 100644 index 00000000000..7ac43cbef99 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest/schema.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS AUDITS; diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java index 85e85c12392..20b0e23ead8 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java @@ -46,7 +46,7 @@ public class UserLastConnectionDatesUpdaterImpl implements UserLastConnectionDat return; } try (DbSession dbSession = dbClient.openSession(false)) { - dbClient.userDao().update(dbSession, user.setLastConnectionDate(now)); + dbClient.userDao().update(dbSession, user.setLastConnectionDate(now), false); dbSession.commit(); } } @@ -59,7 +59,7 @@ public class UserLastConnectionDatesUpdaterImpl implements UserLastConnectionDat return; } try (DbSession dbSession = dbClient.openSession(false)) { - dbClient.userTokenDao().update(dbSession, userToken.setLastConnectionDate(now)); + dbClient.userTokenDao().update(dbSession, userToken.setLastConnectionDate(now), false, null); userToken.setLastConnectionDate(now); dbSession.commit(); } diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java index 21fe881a4a6..dc003eede15 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java @@ -216,7 +216,8 @@ public class UserRegistrarImpl implements UserRegistrar { groupsToAdd.stream().map(groupsByName::get).filter(Objects::nonNull).forEach( groupDto -> { LOGGER.debug("Adding group '{}' to user '{}'", groupDto.getName(), userDto.getLogin()); - dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupUuid(groupDto.getUuid()).setUserUuid(userDto.getUuid())); + dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupUuid(groupDto.getUuid()).setUserUuid(userDto.getUuid()), + groupDto.getName(), userDto.getLogin()); }); } @@ -229,7 +230,7 @@ public class UserRegistrarImpl implements UserRegistrar { .filter(group -> !defaultGroup.isPresent() || !group.getUuid().equals(defaultGroup.get().getUuid())) .forEach(groupDto -> { LOGGER.debug("Removing group '{}' from user '{}'", groupDto.getName(), userDto.getLogin()); - dbClient.userGroupDao().delete(dbSession, groupDto.getUuid(), userDto.getUuid()); + dbClient.userGroupDao().delete(dbSession, groupDto, userDto); }); } diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserUpdater.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserUpdater.java index 3cd64eefd2f..97230b61f9b 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserUpdater.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserUpdater.java @@ -442,6 +442,7 @@ public class UserUpdater { if (isUserAlreadyMemberOfDefaultGroup(defaultGroup, userGroups)) { return; } - dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setUserUuid(userDto.getUuid()).setGroupUuid(defaultGroup.getUuid())); + dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setUserUuid(userDto.getUuid()).setGroupUuid(defaultGroup.getUuid()), + defaultGroup.getName(), userDto.getLogin()); } } diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java index 329082f97aa..e6bb550e9ae 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java @@ -79,7 +79,7 @@ public class UserLastConnectionDatesUpdaterImplTest { public void update_last_connection_date_from_user_token_when_last_connection_was_more_than_one_hour() { UserDto user = db.users().insertUser(); UserTokenDto userToken = db.users().insertToken(user); - db.getDbClient().userTokenDao().update(db.getSession(), userToken.setLastConnectionDate(NOW - TWO_HOUR)); + db.getDbClient().userTokenDao().update(db.getSession(), userToken.setLastConnectionDate(NOW - TWO_HOUR), false, null); db.commit(); underTest.updateLastConnectionDateIfNeeded(userToken); @@ -103,7 +103,7 @@ public class UserLastConnectionDatesUpdaterImplTest { public void do_not_update_when_last_connection_from_user_token_was_less_than_one_hour() { UserDto user = db.users().insertUser(); UserTokenDto userToken = db.users().insertToken(user); - db.getDbClient().userTokenDao().update(db.getSession(), userToken.setLastConnectionDate(NOW - ONE_MINUTE)); + db.getDbClient().userTokenDao().update(db.getSession(), userToken.setLastConnectionDate(NOW - ONE_MINUTE), false, null); db.commit(); underTest.updateLastConnectionDateIfNeeded(userToken); diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java index 926a117ef73..9c9ca2e3a3b 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java @@ -58,7 +58,7 @@ public class DefaultGroupFinderTest { @Test public void fail_with_ISE_when_default_group_does_not_exist() { GroupDto defaultGroup = db.users().insertDefaultGroup(); - db.getDbClient().groupDao().deleteByUuid(db.getSession(), defaultGroup.getUuid()); + db.getDbClient().groupDao().deleteByUuid(db.getSession(), defaultGroup.getUuid(), defaultGroup.getName()); DbSession session = db.getSession(); assertThatThrownBy(() -> underTest.findDefaultGroup(session)) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java index 17ff96f11d1..2b3af10c93b 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java @@ -88,7 +88,7 @@ public class DeactivateAction implements UsersWsAction { dbClient.userTokenDao().deleteByUser(dbSession, user); dbClient.propertiesDao().deleteByKeyAndValue(dbSession, DEFAULT_ISSUE_ASSIGNEE, user.getLogin()); dbClient.propertiesDao().deleteByQuery(dbSession, PropertyQuery.builder().setUserUuid(userUuid).build()); - dbClient.userGroupDao().deleteByUserUuid(dbSession, userUuid); + dbClient.userGroupDao().deleteByUserUuid(dbSession, user); dbClient.userPermissionDao().deleteByUserUuid(dbSession, userUuid); dbClient.permissionTemplateDao().deleteUserPermissionsByUserUuid(dbSession, userUuid); dbClient.qProfileEditUsersDao().deleteByUser(dbSession, user); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SetSettingAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SetSettingAction.java index 88153612323..faa52c9861a 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SetSettingAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SetSettingAction.java @@ -82,7 +82,8 @@ public class SetSettingAction implements UsersWsAction { new UserPropertyDto() .setUserUuid(requireNonNull(userSession.getUuid(), "Authenticated user uuid cannot be null")) .setKey(key) - .setValue(value)); + .setValue(value), + userSession.getLogin()); dbSession.commit(); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java index bfe37380ada..9072d054a0b 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java @@ -82,7 +82,7 @@ public class AddUserAction implements UserGroupsWsAction { if (!isMemberOf(dbSession, user, group)) { UserGroupDto membershipDto = new UserGroupDto().setGroupUuid(group.getUuid()).setUserUuid(user.getUuid()); - dbClient.userGroupDao().insert(dbSession, membershipDto); + dbClient.userGroupDao().insert(dbSession, membershipDto, group.getName(), login); dbSession.commit(); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java index 3a0cb6d1ebf..3b7baca350f 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java @@ -76,7 +76,7 @@ public class DeleteAction implements UserGroupsWsAction { removeFromPermissionTemplates(dbSession, group); removeGroupMembers(dbSession, group); dbClient.qProfileEditGroupsDao().deleteByGroup(dbSession, group); - dbClient.groupDao().deleteByUuid(dbSession, group.getUuid()); + dbClient.groupDao().deleteByUuid(dbSession, group.getUuid(), group.getName()); dbSession.commit(); response.noContent(); @@ -99,6 +99,6 @@ public class DeleteAction implements UserGroupsWsAction { } private void removeGroupMembers(DbSession dbSession, GroupDto group) { - dbClient.userGroupDao().deleteByGroupUuid(dbSession, group.getUuid()); + dbClient.userGroupDao().deleteByGroupUuid(dbSession, group.getUuid(), group.getName()); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java index d0fb9736450..eaf946a5a1d 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java @@ -83,7 +83,7 @@ public class RemoveUserAction implements UserGroupsWsAction { ensureLastAdminIsNotRemoved(dbSession, group, user); - dbClient.userGroupDao().delete(dbSession, group.getUuid(), user.getUuid()); + dbClient.userGroupDao().delete(dbSession, group, user); dbSession.commit(); response.noContent(); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java index 751657e907f..162cf7504c3 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java @@ -117,7 +117,7 @@ public class GenerateAction implements UserTokensWsAction { .setName(name) .setTokenHash(tokenHash) .setCreatedAt(system.now()); - dbClient.userTokenDao().insert(dbSession, userTokenDto); + dbClient.userTokenDao().insert(dbSession, userTokenDto, user.getLogin()); dbSession.commit(); return userTokenDto; } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java index 5f462926b1f..861523e4e21 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java @@ -100,7 +100,7 @@ public class SearchActionTest { UserDto user = db.users().insertUser(); UserTokenDto token1 = db.users().insertToken(user); UserTokenDto token2 = db.users().insertToken(user); - db.getDbClient().userTokenDao().update(db.getSession(), token1.setLastConnectionDate(10_000_000_000L)); + db.getDbClient().userTokenDao().update(db.getSession(), token1.setLastConnectionDate(10_000_000_000L), false, user.getLogin()); db.commit(); logInAsSystemAdministrator(); -- 2.39.5