]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15142 Persisting audits for User operations
authorBelen Pruvost <belen.pruvost@sonarsource.com>
Wed, 14 Jul 2021 15:15:32 +0000 (17:15 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 27 Jul 2021 20:03:02 +0000 (20:03 +0000)
54 files changed:
server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java
server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java
server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/NewValue.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/PropertyNewValue.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserGroupNewValue.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserNewValue.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserTokenNewValue.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/audit/package-info.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/user/GroupDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java
server/sonar-db-dao/src/main/resources/org/sonar/db/audit/AuditMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/schema/schema-sq.ddl
server/sonar-db-dao/src/test/java/org/sonar/db/audit/AuditDaoTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoWithPersisterTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoWithPersisterTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoWithPersisterTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoWithPersisterTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoWithPersisterTest.java [new file with mode: 0644]
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/audit/AuditTesting.java [new file with mode: 0644]
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest/schema.sql [new file with mode: 0644]
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserUpdater.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SetSettingAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java

index 19f2a67693d179724e82f1bf728dc1d5b8bc1e6f..56e16beb4fb6992fc14a5ad18b667013a8bfc2a0 100644 (file)
@@ -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";
index 69fdb4aa868d9effc1e3f96a47c53ee4834d7069..c7e6bdc53e1d34b1781f58855ad361cea2a5390a 100644 (file)
@@ -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";
index 4cae9f55b38995c05d8f0b7b36afe3fb6c9a25eb..7a07e0632e85c0c062a26c9c15c8da492ea5685b 100644 (file)
@@ -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;
 
index 529d9778530d153f1a9d5bb493b01f0887fadbc4..bf6bda78ddb3dab62eaacb856917e9709545cda7 100644 (file)
@@ -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,
index 8b1235d78af7dcaacb9beafc9eab9a0316d48cb2..ea6a0516646bf4525f7283e6b191520f13b62c70 100644 (file)
@@ -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;
   }
index e19514ae10af7d42f2f6b60863c9e190a1d52383..2f070acc78b8875e266174c06b3f09f31e333a18 100644 (file)
@@ -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 (file)
index 0000000..5279b63
--- /dev/null
@@ -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 (file)
index 0000000..45460da
--- /dev/null
@@ -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 (file)
index 0000000..22c7ea2
--- /dev/null
@@ -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 (file)
index 0000000..535f286
--- /dev/null
@@ -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 (file)
index 0000000..b46946c
--- /dev/null
@@ -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 (file)
index 0000000..31d19b3
--- /dev/null
@@ -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 (file)
index 0000000..f22892a
--- /dev/null
@@ -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 (file)
index 0000000..5d072fd
--- /dev/null
@@ -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 (file)
index 0000000..fd12379
--- /dev/null
@@ -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 (file)
index 0000000..1c616da
--- /dev/null
@@ -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;
index a056fba97d60f18081aa05b6203cb333941b8d02..80b1c7dad4e69e42d0f37e85a68b27a2fb05458c 100644 (file)
@@ -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;
   }
 
index 87e77341c715e409227dbbd8d3e5320638b978ca..fda3d03005d6dca6177af890e2a5b38a7c6a7c76 100644 (file)
@@ -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) {
index d2b589cb4efa2e079bdb96d669662274cf2facaa..1c1a3cf2a9083d83690cd8a23db04a0c434d4e71 100644 (file)
@@ -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) {
index 753f0f50320058b20fb6e1761b28f0e40463791b..5f1bc0a022d6e2a3d0a43313aaf3824c0dcd45f4 100644 (file)
 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) {
index d5cac8104a841fae0d8d267bfded7394a88d654f..ed4053f92a6f308cfe644a5d4598a93c0febb91c 100644 (file)
@@ -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 (file)
index 0000000..31ab08d
--- /dev/null
@@ -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>
index 03dd4fd18ceaf5b7994221388fbca6a46c4dd694..a2f2267a9bd69cab989faedb049c1063c32c439a 100644 (file)
@@ -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 (file)
index 0000000..fcde499
--- /dev/null
@@ -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);
+    }
+  }
+}
index 20b0f4489c84d290f8f592a3d5687ae0b8b055c2..a7602c17fa3ffd1264b68d8094596e007fdcc80c 100644 (file)
@@ -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 (file)
index 0000000..1cd59a5
--- /dev/null
@@ -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());
+  }
+}
index 8922f20e22dcff2b2c41b166304a3bd25722d557..596b74f4bc4ac38bfc1d62b10072cb6267b8f9f5 100644 (file)
@@ -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 (file)
index 0000000..541b9b3
--- /dev/null
@@ -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;
+  }
+}
index 690e11a40e48773b14ee7a8ae0fa0de2deb5c320..2daf599a56c527dbd0d6ca1919b1933ba33e93d9 100644 (file)
@@ -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 (file)
index 0000000..942de92
--- /dev/null
@@ -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());
+  }
+}
index 9ebc94e8fc59abd82e8c55007f491a943b98b3d7..1fa36fbd193e872f1589755aa894a67a1d228b0f 100644 (file)
@@ -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 (file)
index 0000000..fbf81f5
--- /dev/null
@@ -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());
+  }
+}
index b56bc154f8e58559783529290baf5a31c8944ed8..e258b5e55ca983a25c7b290c88a702901a2806fa 100644 (file)
@@ -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 (file)
index 0000000..6e8df27
--- /dev/null
@@ -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");
+  }
+}
index c71125da615a09e7ba6bbb99ae71ede9c9b308f6..8d795b2411c925049829fc7396f56802dd2d395a 100644 (file)
@@ -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 (file)
index 0000000..094ae46
--- /dev/null
@@ -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;
+  }
+}
index 73bc44da23c7312e3430df78aa4d5134759f33c8..ac863cf91c825045d23014e1d06439163788daf7 100644 (file)
@@ -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 (file)
index 0000000..b7dd62f
--- /dev/null
@@ -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);
+  }
+}
index 00be71d3e325fa8d5090ced9d5b4351b4ebc5e82..ab24898999fa5439bdb275416b3cdce5dc2eb9c4 100644 (file)
@@ -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 (file)
index 0000000..c1be0fd
--- /dev/null
@@ -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");
+  }
+}
index bab346ea2b5c00d2c068d9613174cb18e5ceb483..0e05e1332e106e095b60b5c62aece801750732dc 100644 (file)
@@ -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 (file)
index 0000000..7ac43cb
--- /dev/null
@@ -0,0 +1 @@
+DROP TABLE IF EXISTS AUDITS;
index 85e85c12392d3ae8a2a479d2c6a08ac7aad5586c..20b0e23ead84651a73c6536fe1679f4be9b5f6c7 100644 (file)
@@ -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();
     }
index 21fe881a4a642f5834d3073a02ff053311fa90ce..dc003eede15e77d8e73517a0c3af7b2c9e7e5316 100644 (file)
@@ -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);
       });
   }
 
index 3cd64eefd2f6f2b13a98b6c949e25cb1b015df20..97230b61f9b7bdcd3dea0b76bf5bf1efb36a49e5 100644 (file)
@@ -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());
   }
 }
index 329082f97aa4c62798bf79b4b4b31372f1b4393b..e6bb550e9aeee0ceebdb563ff6061a2433d2c9e9 100644 (file)
@@ -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);
index 926a117ef73dbd148fab071a6463893f6fdbc6d5..9c9ca2e3a3bc01a79ef8ab3fd9c0aefdf462cd14 100644 (file)
@@ -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))
index 17ff96f11d1ea900b8561ab2664e2977add561b9..2b3af10c93bf493d4466d712b86ffc96990f0808 100644 (file)
@@ -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);
index 8815361232356da4c7aba638fede2113853682c2..faa52c9861ade6f3ff6541b984d1df2b7ed59b0b 100644 (file)
@@ -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();
     }
   }
index bfe37380adaaadfba88e7e81447c87afbe5dbcd0..9072d054a0b3a3f34515f56b179c278da5ce3e65 100644 (file)
@@ -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();
       }
 
index 3a0cb6d1ebf31326127f68b9b9eabc9202d8fb65..3b7baca350f208431e78dfbac67963968e6aa245 100644 (file)
@@ -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());
   }
 }
index d0fb9736450d01585983b0ee61d025a205f91f61..eaf946a5a1df5035643f80f3ee15673ac9ca626f 100644 (file)
@@ -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();
index 751657e907f5902cbfe533e119a857035e563bce..162cf7504c31876e32a28e32214fb6f452a252f9 100644 (file)
@@ -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;
   }
index 5f462926b1f62bffd7be35f53705ac20754965e8..861523e4e21fbecb5ddcc139b6f9360786f28183 100644 (file)
@@ -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();