aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBelen Pruvost <belen.pruvost@sonarsource.com>2021-07-14 17:15:32 +0200
committersonartech <sonartech@sonarsource.com>2021-07-27 20:03:02 +0000
commit09d8569209aade0e3b4cbd21c6dee9da025b245b (patch)
tree938e184ebc8296a3ae821d7225ba3fff82375e75
parent1627d31954c6fcbcbad8bd4045285df8e97cc96b (diff)
downloadsonarqube-09d8569209aade0e3b4cbd21c6dee9da025b245b.tar.gz
sonarqube-09d8569209aade0e3b4cbd21c6dee9da025b245b.zip
SONAR-15142 Persisting audits for User operations
-rw-r--r--server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java17
-rw-r--r--server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java12
-rw-r--r--server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java24
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java7
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDao.java73
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDto.java89
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditMapper.java40
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java58
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/NewValue.java41
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/PropertyNewValue.java67
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserGroupNewValue.java95
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserNewValue.java133
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserTokenNewValue.java83
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/audit/package-info.java23
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/GroupDao.java26
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java25
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java40
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java30
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java33
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/audit/AuditMapper.xml67
-rw-r--r--server/sonar-db-dao/src/schema/schema-sq.ddl12
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/audit/AuditDaoTest.java150
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoTest.java2
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoWithPersisterTest.java107
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java2
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoWithPersisterTest.java169
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoTest.java14
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoWithPersisterTest.java125
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java21
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoWithPersisterTest.java173
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java4
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoWithPersisterTest.java123
-rw-r--r--server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java22
-rw-r--r--server/sonar-db-dao/src/testFixtures/java/org/sonar/db/audit/AuditTesting.java41
-rw-r--r--server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java8
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTable.java73
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java3
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest.java45
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java2
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest/schema.sql1
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java4
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java5
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserUpdater.java3
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java4
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SetSettingAction.java3
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java4
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java2
54 files changed, 2032 insertions, 87 deletions
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<AuditDto> selectAll(DbSession dbSession) {
+ return getMapper(dbSession).selectAll();
+ }
+
+ public List<AuditDto> selectByPeriod(DbSession dbSession, long beginning, long end) {
+ return getMapper(dbSession).selectByPeriod(beginning, end);
+ }
+
+ public List<AuditDto> 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<AuditDto> selectAll();
+
+ List<AuditDto> selectByPeriod(@Param("start") long start, @Param("end") long end);
+
+ List<AuditDto> selectIfBeforeSelectedDate(@Param("end") long end);
+
+ void insert(@Param("dto") AuditDto auditDto);
+
+ void delete(@Param("uuids") List<String> 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<GroupDto> 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<UserPropertyDto> 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<UserPropertyDto> 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 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.audit.AuditMapper">
+
+ <sql id="sqlColumns">
+ 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"
+ </sql>
+
+ <select id="selectAll" resultType="org.sonar.db.audit.AuditDto">
+ select <include refid="sqlColumns"/>
+ from
+ audits a
+ </select>
+
+ <select id="selectByPeriod" parameterType="map" resultType="org.sonar.db.audit.AuditDto">
+ select <include refid="sqlColumns"/>
+ from
+ audits a
+ where
+ a.created_at &gt; #{start, jdbcType=BIGINT} AND
+ a.created_at &lt; #{end, jdbcType=BIGINT}
+ </select>
+
+ <select id="selectIfBeforeSelectedDate" parameterType="map" resultType="org.sonar.db.audit.AuditDto">
+ select <include refid="sqlColumns"/>
+ from
+ audits a
+ where
+ a.created_at &gt; #{end, jdbcType=BIGINT}
+ </select>
+
+ <insert id="insert" parameterType="Map" useGeneratedKeys="false">
+ 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}
+ )
+ </insert>
+
+ <delete id="deleteIfBeforeSelectedDate">
+ delete from audits
+ where
+ created_at &lt;= #{timestamp,jdbcType=BIGINT}
+ </delete>
+
+</mapper>
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<AuditDto> 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<AuditDto> auditDtos = auditDao.selectAll(dbSession);
+
+ assertThat(auditDtos.size()).isEqualTo(100);
+ }
+
+ @Test
+ public void selectByPeriod_selectOneRowFromTheMiddle() {
+ prepareThreeRowsWithDeterministicCreatedAt();
+
+ List<AuditDto> 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<AuditDto> 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<AuditDto> auditDtos = testAuditDao.selectByPeriod(dbSession, 0, 4);
+
+ assertThat(auditDtos.size()).isEqualTo(3);
+ }
+
+ @Test
+ public void selectIfBeforeSelectedDate_select1Row() {
+ prepareThreeRowsWithDeterministicCreatedAt();
+
+ List<AuditDto> auditDtos = testAuditDao.selectIfBeforeSelectedDate(dbSession, 2);
+
+ assertThat(auditDtos.size()).isEqualTo(1);
+ }
+
+ @Test
+ public void deleteIfBeforeSelectedDate_deleteTwoRows() {
+ prepareThreeRowsWithDeterministicCreatedAt();
+
+ testAuditDao.deleteIfBeforeSelectedDate(dbSession, 2);
+
+ List<AuditDto> 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<UserGroupNewValue> 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<UserNewValue> 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<String> 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<String> 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<UserGroupNewValue> 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<String, Object> 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<String, Object> 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<PropertyNewValue> 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<UserTokenNewValue> 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<TestDbImpl> {
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<TestDbImpl> {
}
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<TestDbImpl> {
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<UserPropertyDto>... 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<UserTokenDto>... 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();