Browse Source

SONAR-15142 Persisting audits for User operations

tags/9.1.0.47736
Belen Pruvost 2 years ago
parent
commit
09d8569209
54 changed files with 2032 additions and 87 deletions
  1. 8
    9
      server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java
  2. 6
    6
      server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java
  3. 12
    12
      server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java
  4. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
  5. 7
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
  6. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
  7. 73
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDao.java
  8. 89
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDto.java
  9. 40
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditMapper.java
  10. 58
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java
  11. 41
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/NewValue.java
  12. 67
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/PropertyNewValue.java
  13. 95
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserGroupNewValue.java
  14. 133
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserNewValue.java
  15. 83
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserTokenNewValue.java
  16. 23
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/audit/package-info.java
  17. 24
    2
      server/sonar-db-dao/src/main/java/org/sonar/db/user/GroupDao.java
  18. 25
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java
  19. 34
    6
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java
  20. 29
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java
  21. 31
    2
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java
  22. 67
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/audit/AuditMapper.xml
  23. 12
    0
      server/sonar-db-dao/src/schema/schema-sq.ddl
  24. 150
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/audit/AuditDaoTest.java
  25. 1
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoTest.java
  26. 107
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoWithPersisterTest.java
  27. 1
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
  28. 169
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoWithPersisterTest.java
  29. 7
    7
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoTest.java
  30. 125
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoWithPersisterTest.java
  31. 12
    9
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java
  32. 173
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoWithPersisterTest.java
  33. 2
    2
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java
  34. 123
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoWithPersisterTest.java
  35. 15
    7
      server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java
  36. 41
    0
      server/sonar-db-dao/src/testFixtures/java/org/sonar/db/audit/AuditTesting.java
  37. 4
    4
      server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java
  38. 73
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTable.java
  39. 2
    1
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java
  40. 45
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest.java
  41. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java
  42. 1
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest/schema.sql
  43. 2
    2
      server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java
  44. 3
    2
      server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java
  45. 2
    1
      server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserUpdater.java
  46. 2
    2
      server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java
  47. 1
    1
      server/sonar-webserver-auth/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java
  48. 1
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java
  49. 2
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SetSettingAction.java
  50. 1
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java
  51. 2
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java
  52. 1
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java
  53. 1
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java
  54. 1
    1
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java

+ 8
- 9
server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java View 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";

+ 6
- 6
server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java View 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";

+ 12
- 12
server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlSettings.java View 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;


+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java View 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,

+ 7
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java View 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;
}

+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java View 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,

+ 73
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDao.java View File

@@ -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);
}
}

+ 89
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditDto.java View File

@@ -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;
}
}

+ 40
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditMapper.java View File

@@ -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);

}

+ 58
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/AuditPersister.java View File

@@ -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);
}

+ 41
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/NewValue.java View File

@@ -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("'");
}
}
}

+ 67
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/PropertyNewValue.java View File

@@ -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();
}

}

+ 95
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserGroupNewValue.java View File

@@ -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();
}

}

+ 133
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserNewValue.java View File

@@ -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();
}

}

+ 83
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/model/UserTokenNewValue.java View File

@@ -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();
}

}

+ 23
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/audit/package-info.java View File

@@ -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;

+ 24
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/user/GroupDao.java View 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;
}


+ 25
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java View 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) {

+ 34
- 6
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserGroupDao.java View 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) {

+ 29
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java View File

@@ -20,35 +20,63 @@
package org.sonar.db.user;

import java.util.List;
import javax.annotation.Nullable;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactory;
import org.sonar.db.Dao;
import org.sonar.db.DbSession;
import org.sonar.db.audit.AuditPersister;
import org.sonar.db.audit.model.PropertyNewValue;

public class UserPropertiesDao implements Dao {

private final System2 system2;
private final UuidFactory uuidFactory;

private AuditPersister auditPersister;

public UserPropertiesDao(System2 system2, UuidFactory uuidFactory) {
this.system2 = system2;
this.uuidFactory = uuidFactory;
}

public UserPropertiesDao(System2 system2, UuidFactory uuidFactory, AuditPersister auditPersister) {
this(system2, uuidFactory);
this.auditPersister = auditPersister;
}

public List<UserPropertyDto> selectByUser(DbSession session, UserDto user) {
return mapper(session).selectByUserUuid(user.getUuid());
}

public UserPropertyDto insertOrUpdate(DbSession session, UserPropertyDto dto) {
public UserPropertyDto insertOrUpdate(DbSession session, UserPropertyDto dto, @Nullable String login) {
long now = system2.now();
boolean isUpdate = true;
if (mapper(session).update(dto, now) == 0) {
mapper(session).insert(dto.setUuid(uuidFactory.create()), now);
isUpdate = false;
}

if (auditPersister != null && auditPersister.isTrackedProperty(dto.getKey())) {
if (isUpdate) {
auditPersister.updateUserProperty(session, new PropertyNewValue(dto, login));
} else {
auditPersister.addUserProperty(session, new PropertyNewValue(dto, login));
}
}

return dto;
}

public void deleteByUser(DbSession session, UserDto user) {
List<UserPropertyDto> userProperties = selectByUser(session, user);
mapper(session).deleteByUserUuid(user.getUuid());

if (auditPersister != null) {
userProperties.stream()
.filter(p -> auditPersister.isTrackedProperty(p.getKey()))
.forEach(p -> auditPersister.deleteUserProperty(session, new PropertyNewValue(p, user.getLogin())));
}
}

private static UserPropertiesMapper mapper(DbSession session) {

+ 31
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java View 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) {

+ 67
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/audit/AuditMapper.xml View File

@@ -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>

+ 12
- 0
server/sonar-db-dao/src/schema/schema-sq.ddl View 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,

+ 150
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/audit/AuditDaoTest.java View File

@@ -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);
}
}
}

+ 1
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoTest.java View 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();
}

+ 107
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupDaoWithPersisterTest.java View File

@@ -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());
}
}

+ 1
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java View 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;
}
}

+ 169
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoWithPersisterTest.java View File

@@ -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;
}
}

+ 7
- 7
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoTest.java View 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();

+ 125
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserGroupDaoWithPersisterTest.java View File

@@ -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());
}
}

+ 12
- 9
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java View 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" +

+ 173
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoWithPersisterTest.java View File

@@ -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());
}
}

+ 2
- 2
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java View 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);

+ 123
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoWithPersisterTest.java View File

@@ -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");
}
}

+ 15
- 7
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java View 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() {

+ 41
- 0
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/audit/AuditTesting.java View File

@@ -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;
}
}

+ 4
- 4
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java View 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;
}

+ 73
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTable.java View File

@@ -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);
}
}

+ 2
- 1
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91.java View 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);
}
}

+ 45
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest.java View File

@@ -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");
}
}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v91/DbVersion91Test.java View File

@@ -41,7 +41,7 @@ public class DbVersion91Test {

@Test
public void verify_migration_count() {
verifyMigrationCount(underTest, 6);
verifyMigrationCount(underTest, 7);
}

}

+ 1
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v91/CreateAuditTableTest/schema.sql View File

@@ -0,0 +1 @@
DROP TABLE IF EXISTS AUDITS;

+ 2
- 2
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java View 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();
}

+ 3
- 2
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java View 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);
});
}


+ 2
- 1
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserUpdater.java View 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());
}
}

+ 2
- 2
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java View 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);

+ 1
- 1
server/sonar-webserver-auth/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java View 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))

+ 1
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DeactivateAction.java View 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);

+ 2
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SetSettingAction.java View 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();
}
}

+ 1
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java View 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();
}


+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/DeleteAction.java View 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());
}
}

+ 1
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java View 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();

+ 1
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java View 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;
}

+ 1
- 1
server/sonar-webserver-webapi/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java View 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();


Loading…
Cancel
Save