Browse Source

SONAR-21250 Add validation checks for the Gitlab configuration

tags/10.4.0.87286
Antoine Vigneau 5 months ago
parent
commit
e7ab4f4c88
16 changed files with 401 additions and 157 deletions
  1. 28
    6
      server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidator.java
  2. 81
    19
      server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidatorTest.java
  3. 21
    20
      server/sonar-webserver-common/build.gradle
  4. 110
    42
      server/sonar-webserver-common/src/it/java/org/sonar/server/common/gitlab/config/GitlabConfigurationServiceIT.java
  5. 1
    1
      server/sonar-webserver-common/src/main/java/org/sonar/server/common/gitlab/config/GitlabConfiguration.java
  6. 49
    20
      server/sonar-webserver-common/src/main/java/org/sonar/server/common/gitlab/config/GitlabConfigurationService.java
  7. 25
    0
      server/sonar-webserver-common/src/main/java/org/sonar/server/common/gitlab/config/ProvisioningType.java
  8. 5
    5
      server/sonar-webserver-common/src/main/java/org/sonar/server/common/gitlab/config/UpdateGitlabConfigurationRequest.java
  9. 15
    11
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/DefaultGitlabConfigurationController.java
  10. 3
    2
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationCreateRestRequest.java
  11. 7
    7
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationUpdateRestRequest.java
  12. 8
    3
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/GitlabConfigurationResource.java
  13. 24
    0
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/ProvisioningType.java
  14. 0
    6
      server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
  15. 22
    15
      server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/gitlab/config/DefaultGitlabConfigurationControllerTest.java
  16. 2
    0
      server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

+ 28
- 6
server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidator.java View File

@@ -19,14 +19,19 @@
*/
package org.sonar.alm.client.gitlab;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.config.internal.Encryption;
import org.sonar.api.config.internal.Settings;
import org.sonar.api.server.ServerSide;
import org.sonar.db.alm.setting.AlmSettingDto;

import static com.google.common.base.Strings.isNullOrEmpty;

@ServerSide
public class GitlabGlobalSettingsValidator {

public enum ValidationMode {COMPLETE, AUTH_ONLY}
private final Encryption encryption;
private final GitlabApplicationClient gitlabApplicationClient;

@@ -38,15 +43,32 @@ public class GitlabGlobalSettingsValidator {
public void validate(AlmSettingDto almSettingDto) {
String gitlabUrl = almSettingDto.getUrl();
String accessToken = almSettingDto.getDecryptedPersonalAccessToken(encryption);
validate(ValidationMode.COMPLETE, gitlabUrl, accessToken);
}

if (gitlabUrl == null || accessToken == null) {
throw new IllegalArgumentException("Your Gitlab global configuration is incomplete.");
public void validate(ValidationMode validationMode, @Nullable String gitlabApiUrl, @Nullable String accessToken) {
if (gitlabApiUrl == null) {
throw new IllegalArgumentException("Your Gitlab global configuration is incomplete. The GitLab URL must be set.");
}
gitlabApplicationClient.checkUrl(gitlabApiUrl);
if (ValidationMode.AUTH_ONLY.equals(validationMode)) {
return;
}

gitlabApplicationClient.checkUrl(gitlabUrl);
gitlabApplicationClient.checkToken(gitlabUrl, accessToken);
gitlabApplicationClient.checkReadPermission(gitlabUrl, accessToken);
gitlabApplicationClient.checkWritePermission(gitlabUrl, accessToken);
String decryptedToken = getDecryptedToken(accessToken);
if (decryptedToken == null) {
throw new IllegalArgumentException("Your Gitlab global configuration is incomplete. The GitLab access token must be set.");
}
gitlabApplicationClient.checkToken(gitlabApiUrl, decryptedToken);
gitlabApplicationClient.checkReadPermission(gitlabApiUrl, decryptedToken);
gitlabApplicationClient.checkWritePermission(gitlabApiUrl, decryptedToken);
}

@CheckForNull
public String getDecryptedToken(@Nullable String token) {
if (!isNullOrEmpty(token) && encryption.isEncrypted(token)) {
return encryption.decrypt(token);
}
return token;
}
}

+ 81
- 19
server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidatorTest.java View File

@@ -25,15 +25,22 @@ import org.sonar.api.config.internal.Encryption;
import org.sonar.api.config.internal.Settings;
import org.sonar.db.alm.setting.AlmSettingDto;

import static org.assertj.core.api.Assertions.assertThatException;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator.ValidationMode.AUTH_ONLY;
import static org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator.ValidationMode.COMPLETE;

public class GitlabGlobalSettingsValidatorTest {
private static final Encryption encryption = mock(Encryption.class);
private static final Settings settings = mock(Settings.class);
private static final String GITLAB_API_URL = "https://gitlab.com/api";
private static final String ACCESS_TOKEN = "access-token";
private static final String ENCRYPTED_TOKEN = "encrypted-token";

private final GitlabApplicationClient gitlabHttpClient = mock(GitlabApplicationClient.class);

@@ -41,6 +48,8 @@ public class GitlabGlobalSettingsValidatorTest {

@BeforeClass
public static void setUp() {
when(encryption.isEncrypted(ENCRYPTED_TOKEN)).thenReturn(true);
when(encryption.decrypt(ENCRYPTED_TOKEN)).thenReturn(ACCESS_TOKEN);
when(settings.getEncryption()).thenReturn(encryption);
}

@@ -48,33 +57,30 @@ public class GitlabGlobalSettingsValidatorTest {
public void validate_success() {
String token = "personal-access-token";
AlmSettingDto almSettingDto = new AlmSettingDto()
.setUrl("https://gitlab.com/api")
.setUrl(GITLAB_API_URL)
.setPersonalAccessToken("personal-access-token");
when(encryption.isEncrypted(token)).thenReturn(false);

underTest.validate(almSettingDto);
verify(gitlabHttpClient, times(1)).checkUrl(almSettingDto.getUrl());
verify(gitlabHttpClient, times(1)).checkToken(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
verify(gitlabHttpClient, times(1)).checkReadPermission(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
verify(gitlabHttpClient, times(1)).checkWritePermission(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
verify(gitlabHttpClient).checkUrl(almSettingDto.getUrl());
verify(gitlabHttpClient).checkToken(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
verify(gitlabHttpClient).checkReadPermission(almSettingDto.getUrl(),
almSettingDto.getDecryptedPersonalAccessToken(encryption));
verify(gitlabHttpClient).checkWritePermission(almSettingDto.getUrl(), almSettingDto.getDecryptedPersonalAccessToken(encryption));
}

@Test
public void validate_success_with_encrypted_token() {
String encryptedToken = "personal-access-token";
String decryptedToken = "decrypted-token";
AlmSettingDto almSettingDto = new AlmSettingDto()
.setUrl("https://gitlab.com/api")
.setPersonalAccessToken(encryptedToken);
when(encryption.isEncrypted(encryptedToken)).thenReturn(true);
when(encryption.decrypt(encryptedToken)).thenReturn(decryptedToken);
.setUrl(GITLAB_API_URL)
.setPersonalAccessToken(ENCRYPTED_TOKEN);

underTest.validate(almSettingDto);

verify(gitlabHttpClient, times(1)).checkUrl(almSettingDto.getUrl());
verify(gitlabHttpClient, times(1)).checkToken(almSettingDto.getUrl(), decryptedToken);
verify(gitlabHttpClient, times(1)).checkReadPermission(almSettingDto.getUrl(), decryptedToken);
verify(gitlabHttpClient, times(1)).checkWritePermission(almSettingDto.getUrl(), decryptedToken);
verify(gitlabHttpClient).checkUrl(almSettingDto.getUrl());
verify(gitlabHttpClient).checkToken(almSettingDto.getUrl(), ACCESS_TOKEN);
verify(gitlabHttpClient).checkReadPermission(almSettingDto.getUrl(), ACCESS_TOKEN);
verify(gitlabHttpClient).checkWritePermission(almSettingDto.getUrl(), ACCESS_TOKEN);
}

@Test
@@ -85,18 +91,74 @@ public class GitlabGlobalSettingsValidatorTest {

assertThatThrownBy(() -> underTest.validate(almSettingDto))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Your Gitlab global configuration is incomplete.");
.hasMessage("Your Gitlab global configuration is incomplete. The GitLab URL must be set.");
}

@Test
public void validate_fail_pat_not_set() {
AlmSettingDto almSettingDto = new AlmSettingDto()
.setUrl("https://gitlab.com/api")
.setUrl(GITLAB_API_URL)
.setPersonalAccessToken(null);

assertThatThrownBy(() -> underTest.validate(almSettingDto))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Your Gitlab global configuration is incomplete.");
.hasMessage("Your Gitlab global configuration is incomplete. The GitLab access token must be set.");
}

@Test
public void validate_forAuthOnlyWhenUrlIsNull_throwsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> underTest.validate(AUTH_ONLY, null, null))
.withMessage("Your Gitlab global configuration is incomplete. The GitLab URL must be set.");
}

@Test
public void validate_forAuthOnly_onlyValidatesUrl() {
underTest.validate(AUTH_ONLY, GITLAB_API_URL, null);
verify(gitlabHttpClient).checkUrl(GITLAB_API_URL);
}

@Test
public void validate_whenCompleteMode_validatesUrl() {
assertThatIllegalArgumentException()
.isThrownBy(() -> underTest.validate(COMPLETE, null, null))
.withMessage("Your Gitlab global configuration is incomplete. The GitLab URL must be set.");
}

@Test
public void validate_whenCompleteMode_validatesTokenNotNull() {
assertThatIllegalArgumentException()
.isThrownBy(() -> underTest.validate(COMPLETE, GITLAB_API_URL, null))
.withMessage("Your Gitlab global configuration is incomplete. The GitLab access token must be set.");
}

@Test
public void validate_whenCompleteMode_validatesToken() {
underTest.validate(COMPLETE, GITLAB_API_URL, ACCESS_TOKEN);

verify(gitlabHttpClient).checkUrl(GITLAB_API_URL);
verify(gitlabHttpClient).checkToken(GITLAB_API_URL, ACCESS_TOKEN);
verify(gitlabHttpClient).checkReadPermission(GITLAB_API_URL, ACCESS_TOKEN);
verify(gitlabHttpClient).checkWritePermission(GITLAB_API_URL, ACCESS_TOKEN);
}
@Test
public void validate_whenCompleteModeAndTokenIsEncrypted_decryptAndValidatesToken() {
underTest.validate(COMPLETE, GITLAB_API_URL, ENCRYPTED_TOKEN);

verify(gitlabHttpClient).checkUrl(GITLAB_API_URL);
verify(gitlabHttpClient).checkToken(GITLAB_API_URL, ACCESS_TOKEN);
verify(gitlabHttpClient).checkReadPermission(GITLAB_API_URL, ACCESS_TOKEN);
verify(gitlabHttpClient).checkWritePermission(GITLAB_API_URL, ACCESS_TOKEN);
}

@Test
public void validate_whenCompleteModeAndError_reThrowsError() {
IllegalStateException exception = new IllegalStateException("bla");
doThrow(exception).when(gitlabHttpClient).checkReadPermission(GITLAB_API_URL, ACCESS_TOKEN);

assertThatException()
.isThrownBy(() -> underTest.validate(COMPLETE, GITLAB_API_URL, ACCESS_TOKEN))
.isEqualTo(exception);
}

}

+ 21
- 20
server/sonar-webserver-common/build.gradle View File

@@ -1,32 +1,33 @@
sonar {
properties {
property 'sonar.projectName', "${projectTitle} :: WebServer :: Common"
}
properties {
property 'sonar.projectName', "${projectTitle} :: WebServer :: Common"
}
}

dependencies {
// please keep the list grouped by configuration and ordered by name
api 'com.google.guava:guava'
// please keep the list grouped by configuration and ordered by name
api 'com.google.guava:guava'

api project(':server:sonar-db-dao')
api project(':server:sonar-webserver-auth')
api project(':server:sonar-webserver-ws')
api project(':server:sonar-db-dao')
api project(':server:sonar-webserver-auth')
api project(':server:sonar-webserver-ws')
implementation project(path: ':server:sonar-auth-gitlab')
implementation project(path: ':server:sonar-alm-client')

compileOnlyApi 'com.google.code.findbugs:jsr305'
compileOnlyApi 'javax.servlet:javax.servlet-api'
compileOnlyApi 'javax.servlet:javax.servlet-api'

testImplementation 'org.apache.logging.log4j:log4j-api'
testImplementation 'org.apache.logging.log4j:log4j-core'
testImplementation 'com.google.code.findbugs:jsr305'
testImplementation 'com.tngtech.java:junit-dataprovider'
testImplementation 'org.apache.logging.log4j:log4j-api'
testImplementation 'org.apache.logging.log4j:log4j-core'
testImplementation 'com.google.code.findbugs:jsr305'
testImplementation 'com.tngtech.java:junit-dataprovider'

testImplementation 'junit:junit'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.mockito:mockito-core'
testImplementation 'junit:junit'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.mockito:mockito-core'

testImplementation project(':sonar-testing-harness')
testImplementation testFixtures(project(':server:sonar-db-dao'))
testImplementation testFixtures(project(':server:sonar-server-common'))
testImplementation testFixtures(project(':server:sonar-webserver-api'))
testImplementation project(':sonar-testing-harness')
testImplementation testFixtures(project(':server:sonar-db-dao'))
testImplementation testFixtures(project(':server:sonar-server-common'))
testImplementation testFixtures(project(':server:sonar-webserver-api'))
}

+ 110
- 42
server/sonar-webserver-common/src/it/java/org/sonar/server/common/gitlab/config/GitlabConfigurationServiceIT.java View File

@@ -22,6 +22,7 @@ package org.sonar.server.common.gitlab.config;
import com.google.common.base.Strings;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import org.junit.Before;
@@ -30,6 +31,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator;
import org.sonar.auth.gitlab.GitLabIdentityProvider;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
@@ -43,12 +45,15 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator.ValidationMode.AUTH_ONLY;
import static org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator.ValidationMode.COMPLETE;
import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP;
import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_APPLICATION_ID;
import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_ENABLED;
@@ -61,6 +66,8 @@ import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_URL;
import static org.sonar.server.common.NonNullUpdatedValue.withValueOrThrow;
import static org.sonar.server.common.UpdatedValue.withValue;
import static org.sonar.server.common.gitlab.config.GitlabConfigurationService.UNIQUE_GITLAB_CONFIGURATION_ID;
import static org.sonar.server.common.gitlab.config.ProvisioningType.AUTO_PROVISIONING;
import static org.sonar.server.common.gitlab.config.ProvisioningType.JIT;
import static org.sonar.server.common.gitlab.config.UpdateGitlabConfigurationRequest.builder;

@RunWith(MockitoJUnitRunner.class)
@@ -72,14 +79,18 @@ public class GitlabConfigurationServiceIT {
@Mock
private ManagedInstanceService managedInstanceService;

@Mock
private GitlabGlobalSettingsValidator gitlabGlobalSettingsValidator;

private GitlabConfigurationService gitlabConfigurationService;

@Before
public void setUp() {
when(managedInstanceService.getProviderName()).thenReturn("gitlab");
gitlabConfigurationService = new GitlabConfigurationService(
dbTester.getDbClient(),
managedInstanceService,
dbTester.getDbClient());
gitlabGlobalSettingsValidator);
}

@Test
@@ -99,7 +110,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void getConfiguration_whenConfigurationSet_returnsConfig() {
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(AUTO_PROVISIONING));

GitlabConfiguration configuration = gitlabConfigurationService.getConfiguration("gitlab-configuration");

@@ -119,7 +130,7 @@ public class GitlabConfigurationServiceIT {
assertThat(configuration.url()).isEmpty();
assertThat(configuration.secret()).isEmpty();
assertThat(configuration.synchronizeGroups()).isFalse();
assertThat(configuration.synchronizationType()).isEqualTo(SynchronizationType.JIT);
assertThat(configuration.provisioningType()).isEqualTo(JIT);
assertThat(configuration.allowUsersToSignUp()).isFalse();
assertThat(configuration.provisioningToken()).isNull();
assertThat(configuration.provisioningGroups()).isEmpty();
@@ -127,7 +138,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void updateConfiguration_whenIdIsNotGitlabConfiguration_throwsException() {
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(AUTO_PROVISIONING));
UpdateGitlabConfigurationRequest updateGitlabConfigurationRequest = builder().gitlabConfigurationId("not-gitlab-configuration").build();
assertThatExceptionOfType(NotFoundException.class)
.isThrownBy(() -> gitlabConfigurationService.updateConfiguration(updateGitlabConfigurationRequest))
@@ -144,7 +155,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void updateConfiguration_whenAllUpdateFieldDefined_updatesEverything() {
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.JIT));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(JIT));

UpdateGitlabConfigurationRequest updateRequest = builder()
.gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
@@ -153,7 +164,7 @@ public class GitlabConfigurationServiceIT {
.url(withValueOrThrow("url"))
.secret(withValueOrThrow("secret"))
.synchronizeGroups(withValueOrThrow(true))
.synchronizationType(withValueOrThrow(SynchronizationType.AUTO_PROVISIONING))
.provisioningType(withValueOrThrow(AUTO_PROVISIONING))
.allowUserToSignUp(withValueOrThrow(true))
.provisioningToken(withValueOrThrow("provisioningToken"))
.provisioningGroups(withValueOrThrow(new LinkedHashSet<>(List.of("group1", "group2", "group3"))))
@@ -177,7 +188,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void updateConfiguration_whenAllUpdateFieldDefinedAndSetToFalse_updatesEverything() {
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(AUTO_PROVISIONING));
verify(managedInstanceService).queueSynchronisationTask();
clearInvocations(managedInstanceService);

@@ -185,7 +196,7 @@ public class GitlabConfigurationServiceIT {
.gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
.enabled(withValueOrThrow(false))
.synchronizeGroups(withValueOrThrow(false))
.synchronizationType(withValueOrThrow(SynchronizationType.JIT))
.provisioningType(withValueOrThrow(JIT))
.allowUserToSignUp(withValueOrThrow(false))
.build();

@@ -206,14 +217,14 @@ public class GitlabConfigurationServiceIT {
dbTester.getDbClient().externalGroupDao().insert(dbSession, new ExternalGroupDto("34", "34", GitLabIdentityProvider.KEY));
dbSession.commit();

gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(AUTO_PROVISIONING));
verify(managedInstanceService).queueSynchronisationTask();
reset(managedInstanceService);

UpdateGitlabConfigurationRequest updateRequest = builder()
.gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
.provisioningToken(withValue(null))
.synchronizationType(withValueOrThrow(SynchronizationType.JIT))
.provisioningType(withValueOrThrow(JIT))
.build();

gitlabConfigurationService.updateConfiguration(updateRequest);
@@ -224,7 +235,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void updateConfiguration_whenSwitchingToAutoProvisioningAndTheConfigIsNotEnabled_shouldThrow() {
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.JIT));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(JIT));

UpdateGitlabConfigurationRequest disableRequest = builder()
.gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
@@ -235,7 +246,7 @@ public class GitlabConfigurationServiceIT {

UpdateGitlabConfigurationRequest updateRequest = builder()
.gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
.synchronizationType(withValueOrThrow(SynchronizationType.AUTO_PROVISIONING))
.provisioningType(withValueOrThrow(AUTO_PROVISIONING))
.build();

assertThatThrownBy(() -> gitlabConfigurationService.updateConfiguration(updateRequest))
@@ -246,7 +257,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void updateConfiguration_whenSwitchingToAutoProvisioningAndProvisioningTokenIsNotDefined_shouldThrow() {
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.JIT));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(JIT));

UpdateGitlabConfigurationRequest removeTokenRequest = builder()
.gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
@@ -257,7 +268,7 @@ public class GitlabConfigurationServiceIT {

UpdateGitlabConfigurationRequest updateRequest = builder()
.gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
.synchronizationType(withValueOrThrow(SynchronizationType.AUTO_PROVISIONING))
.provisioningType(withValueOrThrow(AUTO_PROVISIONING))
.build();

assertThatThrownBy(() -> gitlabConfigurationService.updateConfiguration(updateRequest))
@@ -274,7 +285,7 @@ public class GitlabConfigurationServiceIT {
assertThat(configuration.url()).isEqualTo("url");
assertThat(configuration.secret()).isEqualTo("secret");
assertThat(configuration.synchronizeGroups()).isTrue();
assertThat(configuration.synchronizationType()).isEqualTo(SynchronizationType.AUTO_PROVISIONING);
assertThat(configuration.provisioningType()).isEqualTo(AUTO_PROVISIONING);
assertThat(configuration.allowUsersToSignUp()).isTrue();
assertThat(configuration.provisioningToken()).isEqualTo("provisioningToken");
assertThat(configuration.provisioningGroups()).containsExactlyInAnyOrder("group1", "group2", "group3");
@@ -282,7 +293,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void createConfiguration_whenConfigurationAlreadyExists_shouldThrow() {
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(AUTO_PROVISIONING);
gitlabConfigurationService.createConfiguration(gitlabConfiguration);

assertThatThrownBy(() -> gitlabConfigurationService.createConfiguration(gitlabConfiguration))
@@ -292,11 +303,11 @@ public class GitlabConfigurationServiceIT {

@Test
public void createConfiguration_whenAutoProvisioning_shouldCreateCorrectConfigurationAndScheduleSync() {
GitlabConfiguration configuration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
GitlabConfiguration configuration = buildGitlabConfiguration(AUTO_PROVISIONING);

GitlabConfiguration createdConfiguration = gitlabConfigurationService.createConfiguration(configuration);

assertThat(createdConfiguration).isEqualTo(configuration);
assertConfigurationIsCorrect(configuration, createdConfiguration);

verifyCommonSettings(configuration);

@@ -313,7 +324,7 @@ public class GitlabConfigurationServiceIT {
"url",
"secret",
true,
SynchronizationType.AUTO_PROVISIONING,
AUTO_PROVISIONING,
true,
null,
Set.of("group1", "group2", "group3"));
@@ -326,7 +337,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void createConfiguration_whenInstanceIsExternallyManaged_shouldThrow() {
GitlabConfiguration configuration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
GitlabConfiguration configuration = buildGitlabConfiguration(AUTO_PROVISIONING);

when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
when(managedInstanceService.getProviderName()).thenReturn("not-gitlab");
@@ -339,11 +350,11 @@ public class GitlabConfigurationServiceIT {

@Test
public void createConfiguration_whenJitProvisioning_shouldCreateCorrectConfiguration() {
GitlabConfiguration configuration = buildGitlabConfiguration(SynchronizationType.JIT);
GitlabConfiguration configuration = buildGitlabConfiguration(JIT);

GitlabConfiguration createdConfiguration = gitlabConfigurationService.createConfiguration(configuration);

assertThat(createdConfiguration).isEqualTo(configuration);
assertConfigurationIsCorrect(configuration, createdConfiguration);

verifyCommonSettings(configuration);
verifyNoInteractions(managedInstanceService);
@@ -359,14 +370,14 @@ public class GitlabConfigurationServiceIT {
"url",
"secret",
true,
SynchronizationType.JIT,
JIT,
true,
null,
Set.of("group1", "group2", "group3"));

GitlabConfiguration createdConfiguration = gitlabConfigurationService.createConfiguration(configuration);

assertThat(createdConfiguration).isEqualTo(configuration);
assertConfigurationIsCorrect(configuration, createdConfiguration);

verifyCommonSettings(configuration);
verifyNoInteractions(managedInstanceService);
@@ -383,7 +394,7 @@ public class GitlabConfigurationServiceIT {
verifySettingWasSet(GITLAB_AUTH_PROVISIONING_TOKEN, Strings.nullToEmpty(configuration.provisioningToken()));
verifySettingWasSet(GITLAB_AUTH_PROVISIONING_GROUPS, String.join(",", configuration.provisioningGroups()));
verifySettingWasSet(GITLAB_AUTH_PROVISIONING_ENABLED,
String.valueOf(configuration.synchronizationType().equals(SynchronizationType.AUTO_PROVISIONING)));
String.valueOf(configuration.provisioningType().equals(AUTO_PROVISIONING)));
}

private void verifySettingWasSet(String setting, @Nullable String value) {
@@ -410,7 +421,7 @@ public class GitlabConfigurationServiceIT {
dbTester.getDbClient().externalGroupDao().insert(dbSession, new ExternalGroupDto("12", "12", GitLabIdentityProvider.KEY));
dbTester.getDbClient().externalGroupDao().insert(dbSession, new ExternalGroupDto("34", "34", GitLabIdentityProvider.KEY));
dbSession.commit();
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(AUTO_PROVISIONING));
gitlabConfigurationService.deleteConfiguration("gitlab-configuration");

assertPropertyIsDeleted(GITLAB_AUTH_ENABLED);
@@ -432,7 +443,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void triggerRun_whenConfigIsCorrect_shouldTriggerSync() {
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(AUTO_PROVISIONING));
reset(managedInstanceService);

gitlabConfigurationService.triggerRun();
@@ -442,7 +453,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void triggerRun_whenConfigIsForJit_shouldThrow() {
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.JIT));
gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(JIT));

assertThatIllegalStateException()
.isThrownBy(() -> gitlabConfigurationService.triggerRun())
@@ -451,7 +462,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void triggerRun_whenConfigIsDisabled_shouldThrow() {
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(AUTO_PROVISIONING);
gitlabConfigurationService.createConfiguration(gitlabConfiguration);
gitlabConfigurationService.updateConfiguration(builder().gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID).enabled(withValueOrThrow(false)).build());

@@ -462,7 +473,7 @@ public class GitlabConfigurationServiceIT {

@Test
public void triggerRun_whenProvisioningTokenIsNotSet_shouldThrow() {
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(AUTO_PROVISIONING);
gitlabConfigurationService.createConfiguration(gitlabConfiguration);
gitlabConfigurationService.updateConfiguration(builder().gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID).provisioningToken(withValue(null)).build());

@@ -471,17 +482,74 @@ public class GitlabConfigurationServiceIT {
.withMessage("Provisioning token must be set to enable GitLab provisioning.");
}

private static GitlabConfiguration buildGitlabConfiguration(SynchronizationType synchronizationType) {
return new GitlabConfiguration(
UNIQUE_GITLAB_CONFIGURATION_ID,
true,
"applicationId",
"url",
"secret",
true,
synchronizationType,
true,
"provisioningToken",
Set.of("group1", "group2", "group3"));
@Test
public void validate_whenConfigurationIsDisabled_shouldNotValidate() {
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(AUTO_PROVISIONING);
when(gitlabConfiguration.enabled()).thenReturn(false);

gitlabConfigurationService.validate(gitlabConfiguration);

verifyNoInteractions(gitlabGlobalSettingsValidator);
}

@Test
public void validate_whenConfigurationIsValidAndJIT_returnEmptyOptional() {
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(JIT);
when(gitlabConfiguration.enabled()).thenReturn(true);

gitlabConfigurationService.validate(gitlabConfiguration);

verify(gitlabGlobalSettingsValidator).validate(AUTH_ONLY, gitlabConfiguration.url() + "/api/v4", gitlabConfiguration.provisioningToken());
}

@Test
public void validate_whenConfigurationIsValidAndAutoProvisioning_returnEmptyOptional() {
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(AUTO_PROVISIONING);
when(gitlabConfiguration.enabled()).thenReturn(true);

gitlabConfigurationService.validate(gitlabConfiguration);

verify(gitlabGlobalSettingsValidator).validate(COMPLETE, gitlabConfiguration.url() + "/api/v4", gitlabConfiguration.provisioningToken());
}

@Test
public void validate_whenConfigurationIsInValid_returnsExceptionMessage() {
GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(AUTO_PROVISIONING);
when(gitlabConfiguration.enabled()).thenReturn(true);

Exception exception = new IllegalStateException("Invalid configuration");
when(gitlabConfigurationService.validate(gitlabConfiguration)).thenThrow(exception);

Optional<String> message = gitlabConfigurationService.validate(gitlabConfiguration);

assertThat(message).contains("Invalid configuration");
}

private static GitlabConfiguration buildGitlabConfiguration(ProvisioningType provisioningType) {
GitlabConfiguration gitlabConfiguration = mock();
when(gitlabConfiguration.id()).thenReturn("gitlab-configuration");
when(gitlabConfiguration.enabled()).thenReturn(true);
when(gitlabConfiguration.applicationId()).thenReturn("applicationId");
when(gitlabConfiguration.url()).thenReturn("url");
when(gitlabConfiguration.secret()).thenReturn("secret");
when(gitlabConfiguration.synchronizeGroups()).thenReturn(true);
when(gitlabConfiguration.provisioningType()).thenReturn(provisioningType);
when(gitlabConfiguration.allowUsersToSignUp()).thenReturn(true);
when(gitlabConfiguration.provisioningToken()).thenReturn("provisioningToken");
when(gitlabConfiguration.provisioningGroups()).thenReturn(new LinkedHashSet<>(Set.of("group1", "group2", "group3")));
return gitlabConfiguration;
}

private static void assertConfigurationIsCorrect(GitlabConfiguration expectedConfiguration, GitlabConfiguration actualConfiguration) {
assertThat(actualConfiguration.id()).isEqualTo(expectedConfiguration.id());
assertThat(actualConfiguration.enabled()).isEqualTo(expectedConfiguration.enabled());
assertThat(actualConfiguration.applicationId()).isEqualTo(expectedConfiguration.applicationId());
assertThat(actualConfiguration.url()).isEqualTo(expectedConfiguration.url());
assertThat(actualConfiguration.secret()).isEqualTo(expectedConfiguration.secret());
assertThat(actualConfiguration.synchronizeGroups()).isEqualTo(expectedConfiguration.synchronizeGroups());
assertThat(actualConfiguration.provisioningType()).isEqualTo(expectedConfiguration.provisioningType());
assertThat(actualConfiguration.allowUsersToSignUp()).isEqualTo(expectedConfiguration.allowUsersToSignUp());
assertThat(actualConfiguration.provisioningToken()).isEqualTo(expectedConfiguration.provisioningToken());
assertThat(actualConfiguration.provisioningGroups()).containsExactlyInAnyOrderElementsOf(expectedConfiguration.provisioningGroups());
}
}

+ 1
- 1
server/sonar-webserver-common/src/main/java/org/sonar/server/common/gitlab/config/GitlabConfiguration.java View File

@@ -35,7 +35,7 @@ public record GitlabConfiguration(

boolean synchronizeGroups,

SynchronizationType synchronizationType,
ProvisioningType provisioningType,

boolean allowUsersToSignUp,


+ 49
- 20
server/sonar-webserver-common/src/main/java/org/sonar/server/common/gitlab/config/GitlabConfigurationService.java View File

@@ -26,7 +26,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.sonar.api.utils.Preconditions;
import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator;
import org.sonar.api.server.ServerSide;
import org.sonar.auth.gitlab.GitLabIdentityProvider;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
@@ -38,6 +39,8 @@ import org.sonar.server.management.ManagedInstanceService;

import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator.ValidationMode.AUTH_ONLY;
import static org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator.ValidationMode.COMPLETE;
import static org.sonar.api.utils.Preconditions.checkState;
import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP;
import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_APPLICATION_ID;
@@ -48,9 +51,11 @@ import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_TOKE
import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_SECRET;
import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_SYNC_USER_GROUPS;
import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_URL;
import static org.sonar.server.common.gitlab.config.SynchronizationType.AUTO_PROVISIONING;
import static org.sonar.server.common.gitlab.config.ProvisioningType.AUTO_PROVISIONING;
import static org.sonar.server.common.gitlab.config.ProvisioningType.JIT;
import static org.sonar.server.exceptions.NotFoundException.checkFound;

@ServerSide
public class GitlabConfigurationService {

private static final List<String> GITLAB_CONFIGURATION_PROPERTIES = List.of(
@@ -65,17 +70,20 @@ public class GitlabConfigurationService {
GITLAB_AUTH_PROVISIONING_GROUPS);

public static final String UNIQUE_GITLAB_CONFIGURATION_ID = "gitlab-configuration";
private final ManagedInstanceService managedInstanceService;
private final DbClient dbClient;
private final ManagedInstanceService managedInstanceService;
private final GitlabGlobalSettingsValidator gitlabGlobalSettingsValidator;

public GitlabConfigurationService(ManagedInstanceService managedInstanceService, DbClient dbClient) {
this.managedInstanceService = managedInstanceService;
public GitlabConfigurationService(DbClient dbClient,
ManagedInstanceService managedInstanceService, GitlabGlobalSettingsValidator gitlabGlobalSettingsValidator) {
this.dbClient = dbClient;
this.managedInstanceService = managedInstanceService;
this.gitlabGlobalSettingsValidator = gitlabGlobalSettingsValidator;
}

public GitlabConfiguration updateConfiguration(UpdateGitlabConfigurationRequest updateRequest) {
UpdatedValue<Boolean> provisioningEnabled =
updateRequest.synchronizationType().map(GitlabConfigurationService::shouldEnableAutoProvisioning);
updateRequest.provisioningType().map(GitlabConfigurationService::shouldEnableAutoProvisioning);
try (DbSession dbSession = dbClient.openSession(true)) {
throwIfConfigurationDoesntExist(dbSession);
GitlabConfiguration currentConfiguration = getConfiguration(updateRequest.gitlabConfigurationId(), dbSession);
@@ -89,8 +97,8 @@ public class GitlabConfigurationService {
setIfDefined(dbSession, GITLAB_AUTH_PROVISIONING_TOKEN, updateRequest.provisioningToken());
setIfDefined(dbSession, GITLAB_AUTH_PROVISIONING_GROUPS, updateRequest.provisioningGroups().map(groups -> String.join(",", groups)));
boolean shouldTriggerProvisioning =
provisioningEnabled.orElse(false) && !currentConfiguration.synchronizationType().equals(AUTO_PROVISIONING);
deleteExternalGroupsWhenDisablingAutoProvisioning(dbSession, currentConfiguration, updateRequest.synchronizationType());
provisioningEnabled.orElse(false) && !currentConfiguration.provisioningType().equals(AUTO_PROVISIONING);
deleteExternalGroupsWhenDisablingAutoProvisioning(dbSession, currentConfiguration, updateRequest.provisioningType());
GitlabConfiguration updatedConfiguration = getConfiguration(UNIQUE_GITLAB_CONFIGURATION_ID, dbSession);
if (shouldTriggerProvisioning) {
triggerRun(updatedConfiguration);
@@ -109,9 +117,10 @@ public class GitlabConfigurationService {
private void deleteExternalGroupsWhenDisablingAutoProvisioning(
DbSession dbSession,
GitlabConfiguration currentConfiguration,
UpdatedValue<SynchronizationType> synchronizationTypeFromUpdate) {
boolean disableAutoProvisioning = synchronizationTypeFromUpdate.map(synchronizationType -> synchronizationType.equals(SynchronizationType.JIT)).orElse(false)
&& currentConfiguration.synchronizationType().equals(AUTO_PROVISIONING);
UpdatedValue<ProvisioningType> provisioningTypeFromUpdate) {
boolean disableAutoProvisioning =
provisioningTypeFromUpdate.map(provisioningType -> provisioningType.equals(JIT)).orElse(false)
&& currentConfiguration.provisioningType().equals(AUTO_PROVISIONING);
if (disableAutoProvisioning) {
dbClient.externalGroupDao().deleteByExternalIdentityProvider(dbSession, GitLabIdentityProvider.KEY);
}
@@ -169,14 +178,14 @@ public class GitlabConfigurationService {
checkFound(dbClient.propertiesDao().selectGlobalProperty(dbSession, GITLAB_AUTH_ENABLED), "GitLab configuration doesn't exist.");
}

private static SynchronizationType toSynchronizationType(boolean provisioningEnabled) {
return provisioningEnabled ? AUTO_PROVISIONING : SynchronizationType.JIT;
private static ProvisioningType toProvisioningType(boolean provisioningEnabled) {
return provisioningEnabled ? AUTO_PROVISIONING : JIT;
}

public GitlabConfiguration createConfiguration(GitlabConfiguration configuration) {
throwIfConfigurationAlreadyExists();

boolean enableAutoProvisioning = shouldEnableAutoProvisioning(configuration.synchronizationType());
boolean enableAutoProvisioning = shouldEnableAutoProvisioning(configuration.provisioningType());
try (DbSession dbSession = dbClient.openSession(false)) {
setProperty(dbSession, GITLAB_AUTH_ENABLED, String.valueOf(configuration.enabled()));
setProperty(dbSession, GITLAB_AUTH_APPLICATION_ID, configuration.applicationId());
@@ -203,8 +212,8 @@ public class GitlabConfigurationService {
});
}

private static boolean shouldEnableAutoProvisioning(SynchronizationType synchronizationType) {
return AUTO_PROVISIONING.equals(synchronizationType);
private static boolean shouldEnableAutoProvisioning(ProvisioningType provisioningType) {
return AUTO_PROVISIONING.equals(provisioningType);
}

private void setProperty(DbSession dbSession, String propertyName, @Nullable String value) {
@@ -221,7 +230,7 @@ public class GitlabConfigurationService {
getStringPropertyOrEmpty(dbSession, GITLAB_AUTH_URL),
getStringPropertyOrEmpty(dbSession, GITLAB_AUTH_SECRET),
getBooleanOrFalse(dbSession, GITLAB_AUTH_SYNC_USER_GROUPS),
toSynchronizationType(getBooleanOrFalse(dbSession, GITLAB_AUTH_PROVISIONING_ENABLED)),
toProvisioningType(getBooleanOrFalse(dbSession, GITLAB_AUTH_PROVISIONING_ENABLED)),
getBooleanOrFalse(dbSession, GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP),
getStringPropertyOrNull(dbSession, GITLAB_AUTH_PROVISIONING_TOKEN),
getProvisioningGroups(dbSession)
@@ -244,13 +253,12 @@ public class GitlabConfigurationService {
private void triggerRun(GitlabConfiguration gitlabConfiguration) {
throwIfConfigIncompleteOrInstanceAlreadyManaged(gitlabConfiguration);
managedInstanceService.queueSynchronisationTask();

}

private void throwIfConfigIncompleteOrInstanceAlreadyManaged(GitlabConfiguration configuration) {
checkInstanceNotManagedByAnotherProvider();
Preconditions.checkState(AUTO_PROVISIONING.equals(configuration.synchronizationType()), "Auto provisioning must be activated");
Preconditions.checkState(configuration.enabled(), getErrorMessage("GitLab authentication must be turned on"));
checkState(AUTO_PROVISIONING.equals(configuration.provisioningType()), "Auto provisioning must be activated");
checkState(configuration.enabled(), getErrorMessage("GitLab authentication must be turned on"));
checkState(isNotBlank(configuration.provisioningToken()), getErrorMessage("Provisioning token must be set"));
}

@@ -267,4 +275,25 @@ public class GitlabConfigurationService {
private static String getErrorMessage(String prefix) {
return format("%s to enable GitLab provisioning.", prefix);
}

public Optional<String> validate(GitlabConfiguration gitlabConfiguration) {
if (!gitlabConfiguration.enabled()) {
return Optional.empty();
}
String url = (gitlabConfiguration.url() + "/api/v4").replace("//", "/");
try {
gitlabGlobalSettingsValidator.validate(
toValidationMode(gitlabConfiguration.provisioningType()),
url,
gitlabConfiguration.provisioningToken()
);
} catch (Exception e) {
return Optional.of(e.getMessage());
}
return Optional.empty();
}

private static GitlabGlobalSettingsValidator.ValidationMode toValidationMode(ProvisioningType provisioningType) {
return AUTO_PROVISIONING.equals(provisioningType) ? COMPLETE : AUTH_ONLY;
}
}

+ 25
- 0
server/sonar-webserver-common/src/main/java/org/sonar/server/common/gitlab/config/ProvisioningType.java View File

@@ -0,0 +1,25 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.common.gitlab.config;

public enum ProvisioningType {
JIT,
AUTO_PROVISIONING
}

+ 5
- 5
server/sonar-webserver-common/src/main/java/org/sonar/server/common/gitlab/config/UpdateGitlabConfigurationRequest.java View File

@@ -30,7 +30,7 @@ public record UpdateGitlabConfigurationRequest(
NonNullUpdatedValue<String> url,
NonNullUpdatedValue<String> secret,
NonNullUpdatedValue<Boolean> synchronizeGroups,
NonNullUpdatedValue<SynchronizationType> synchronizationType,
NonNullUpdatedValue<ProvisioningType> provisioningType,
NonNullUpdatedValue<Boolean> allowUsersToSignUp,
UpdatedValue<String> provisioningToken,
NonNullUpdatedValue<Set<String>> provisioningGroups
@@ -47,7 +47,7 @@ public record UpdateGitlabConfigurationRequest(
private NonNullUpdatedValue<String> url = NonNullUpdatedValue.undefined();
private NonNullUpdatedValue<String> secret = NonNullUpdatedValue.undefined();
private NonNullUpdatedValue<Boolean> synchronizeGroups = NonNullUpdatedValue.undefined();
private NonNullUpdatedValue<SynchronizationType> synchronizationType = NonNullUpdatedValue.undefined();
private NonNullUpdatedValue<ProvisioningType> provisioningType = NonNullUpdatedValue.undefined();
private NonNullUpdatedValue<Boolean> allowUserToSignUp = NonNullUpdatedValue.undefined();
private UpdatedValue<String> provisioningToken = UpdatedValue.undefined();
private NonNullUpdatedValue<Set<String>> provisioningGroups = NonNullUpdatedValue.undefined();
@@ -85,8 +85,8 @@ public record UpdateGitlabConfigurationRequest(
return this;
}

public Builder synchronizationType(NonNullUpdatedValue<SynchronizationType> synchronizationType) {
this.synchronizationType = synchronizationType;
public Builder provisioningType(NonNullUpdatedValue<ProvisioningType> provisioningType) {
this.provisioningType = provisioningType;
return this;
}

@@ -106,7 +106,7 @@ public record UpdateGitlabConfigurationRequest(
}

public UpdateGitlabConfigurationRequest build() {
return new UpdateGitlabConfigurationRequest(gitlabConfigurationId, enabled, applicationId, url, secret, synchronizeGroups, synchronizationType, allowUserToSignUp,
return new UpdateGitlabConfigurationRequest(gitlabConfigurationId, enabled, applicationId, url, secret, synchronizeGroups, provisioningType, allowUserToSignUp,
provisioningToken, provisioningGroups);
}
}

+ 15
- 11
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/DefaultGitlabConfigurationController.java View File

@@ -21,10 +21,11 @@ package org.sonar.server.v2.api.gitlab.config.controller;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.sonar.server.common.gitlab.config.GitlabConfiguration;
import org.sonar.server.common.gitlab.config.GitlabConfigurationService;
import org.sonar.server.common.gitlab.config.SynchronizationType;
import org.sonar.server.common.gitlab.config.ProvisioningType;
import org.sonar.server.common.gitlab.config.UpdateGitlabConfigurationRequest;
import org.sonar.server.user.UserSession;
import org.sonar.server.v2.api.gitlab.config.request.GitlabConfigurationCreateRestRequest;
@@ -57,7 +58,7 @@ public class DefaultGitlabConfigurationController implements GitlabConfiguration

List<GitlabConfigurationResource> gitlabConfigurationResources = gitlabConfigurationService.findConfigurations()
.stream()
.map(DefaultGitlabConfigurationController::toGitLabConfigurationResource)
.map(this::toGitLabConfigurationResource)
.toList();

PageRestResponse pageRestResponse = new PageRestResponse(1, 1000, gitlabConfigurationResources.size());
@@ -80,7 +81,7 @@ public class DefaultGitlabConfigurationController implements GitlabConfiguration
createRestRequest.url(),
createRestRequest.secret(),
createRestRequest.synchronizeGroups(),
toSynchronizationType(createRestRequest.synchronizationType()),
toProvisioningType(createRestRequest.provisioningType()),
createRestRequest.allowUsersToSignUp() != null && createRestRequest.allowUsersToSignUp(),
createRestRequest.provisioningToken(),
createRestRequest.provisioningGroups() == null ? Set.of() : Set.copyOf(createRestRequest.provisioningGroups()));
@@ -106,7 +107,7 @@ public class DefaultGitlabConfigurationController implements GitlabConfiguration
.url(updateRequest.getUrl().toNonNullUpdatedValue())
.secret(updateRequest.getSecret().toNonNullUpdatedValue())
.synchronizeGroups(updateRequest.getSynchronizeGroups().toNonNullUpdatedValue())
.synchronizationType(updateRequest.getSynchronizationType().map(DefaultGitlabConfigurationController::toSynchronizationType).toNonNullUpdatedValue())
.provisioningType(updateRequest.getProvisioningType().map(DefaultGitlabConfigurationController::toProvisioningType).toNonNullUpdatedValue())
.allowUserToSignUp(updateRequest.getAllowUsersToSignUp().toNonNullUpdatedValue())
.provisioningToken(updateRequest.getProvisioningToken().toUpdatedValue())
.provisioningGroups(updateRequest.getProvisioningGroups().map(DefaultGitlabConfigurationController::getGroups).toNonNullUpdatedValue())
@@ -117,24 +118,27 @@ public class DefaultGitlabConfigurationController implements GitlabConfiguration
return new HashSet<>(groups);
}

private static GitlabConfigurationResource toGitLabConfigurationResource(GitlabConfiguration configuration) {
private GitlabConfigurationResource toGitLabConfigurationResource(GitlabConfiguration configuration) {
Optional<String> configurationError = gitlabConfigurationService.validate(configuration);
return new GitlabConfigurationResource(
configuration.id(),
configuration.enabled(),
configuration.applicationId(),
configuration.url(),
configuration.synchronizeGroups(),
toRestSynchronizationType(configuration),
toRestProvisioningType(configuration),
configuration.allowUsersToSignUp(),
sortGroups(configuration.provisioningGroups()));
sortGroups(configuration.provisioningGroups()),
configurationError.orElse(null)
);
}

private static SynchronizationType toRestSynchronizationType(GitlabConfiguration configuration) {
return SynchronizationType.valueOf(configuration.synchronizationType().name());
private static org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType toRestProvisioningType(GitlabConfiguration configuration) {
return org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType.valueOf(configuration.provisioningType().name());
}

private static SynchronizationType toSynchronizationType(SynchronizationType synchronizationType) {
return SynchronizationType.valueOf(synchronizationType.name());
private static ProvisioningType toProvisioningType(org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType provisioningType) {
return ProvisioningType.valueOf(provisioningType.name());
}

private static List<String> sortGroups(Set<String> groups) {

+ 3
- 2
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationCreateRestRequest.java View File

@@ -25,7 +25,7 @@ import java.util.List;
import javax.annotation.Nullable;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.sonar.server.common.gitlab.config.SynchronizationType;
import org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType;

public record GitlabConfigurationCreateRestRequest(

@@ -49,7 +49,8 @@ public record GitlabConfigurationCreateRestRequest(

@NotNull
@Schema(description = "Type of synchronization")
SynchronizationType synchronizationType,
ProvisioningType provisioningType,

@Nullable
@Schema(accessMode = Schema.AccessMode.WRITE_ONLY, description = "Gitlab token for provisioning")
String provisioningToken,

+ 7
- 7
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationUpdateRestRequest.java View File

@@ -23,7 +23,7 @@ import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import javax.validation.constraints.Size;
import org.sonar.server.common.gitlab.config.SynchronizationType;
import org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType;
import org.sonar.server.v2.common.model.UpdateField;

public class GitlabConfigurationUpdateRestRequest {
@@ -33,7 +33,7 @@ public class GitlabConfigurationUpdateRestRequest {
private UpdateField<String> url = UpdateField.undefined();
private UpdateField<String> secret = UpdateField.undefined();
private UpdateField<Boolean> synchronizeGroups = UpdateField.undefined();
private UpdateField<SynchronizationType> synchronizationType = UpdateField.undefined();
private UpdateField<ProvisioningType> provisioningType = UpdateField.undefined();
private UpdateField<Boolean> allowUsersToSignUp = UpdateField.undefined();
private UpdateField<String> provisioningToken = UpdateField.undefined();
private UpdateField<List<String>> provisioningGroups = UpdateField.undefined();
@@ -83,13 +83,13 @@ public class GitlabConfigurationUpdateRestRequest {
this.synchronizeGroups = UpdateField.withValue(synchronizeGroups);
}

@Schema(implementation = SynchronizationType.class, description = "Type of synchronization")
public UpdateField<SynchronizationType> getSynchronizationType() {
return synchronizationType;
@Schema(implementation = ProvisioningType.class, description = "Type of synchronization")
public UpdateField<ProvisioningType> getProvisioningType() {
return provisioningType;
}

public void setSynchronizationType(SynchronizationType synchronizationType) {
this.synchronizationType = UpdateField.withValue(synchronizationType);
public void setProvisioningType(ProvisioningType provisioningType) {
this.provisioningType = UpdateField.withValue(provisioningType);
}

@Schema(implementation = Boolean.class, description = "Allow user to sign up")

+ 8
- 3
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/GitlabConfigurationResource.java View File

@@ -21,7 +21,7 @@ package org.sonar.server.v2.api.gitlab.config.resource;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import org.sonar.server.common.gitlab.config.SynchronizationType;
import javax.annotation.Nullable;

public record GitlabConfigurationResource(

@@ -38,11 +38,16 @@ public record GitlabConfigurationResource(

boolean synchronizeGroups,

SynchronizationType synchronizationType,
ProvisioningType provisioningType,

boolean allowUsersToSignUp,

@Schema(description = "Root Gitlab groups to provision")
List<String> provisioningGroups
List<String> provisioningGroups,

@Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "In case the GitLab configuration is incorrect, error message")
@Nullable
String errorMessage
) {
}


+ 24
- 0
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/ProvisioningType.java View File

@@ -0,0 +1,24 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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.v2.api.gitlab.config.resource;

public enum ProvisioningType {
JIT, AUTO_PROVISIONING
}

+ 0
- 6
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java View File

@@ -36,7 +36,6 @@ import org.sonar.server.common.rule.service.RuleService;
import org.sonar.server.common.text.MacroInterpreter;
import org.sonar.server.common.user.service.UserService;
import org.sonar.server.health.HealthChecker;
import org.sonar.server.management.ManagedInstanceService;
import org.sonar.server.platform.NodeInformation;
import org.sonar.server.rule.RuleDescriptionFormatter;
import org.sonar.server.user.SystemPasscode;
@@ -125,11 +124,6 @@ public class PlatformLevel4WebConfig {
return handlerMapping;
}

@Bean
public GitlabConfigurationService gitlabConfigurationService(ManagedInstanceService managedInstanceService, DbClient dbClient) {
return new GitlabConfigurationService( managedInstanceService, dbClient);
}

@Bean
public GitlabConfigurationController gitlabConfigurationController(UserSession userSession, GitlabConfigurationService gitlabConfigurationService) {
return new DefaultGitlabConfigurationController(userSession, gitlabConfigurationService);

+ 22
- 15
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/gitlab/config/DefaultGitlabConfigurationControllerTest.java View File

@@ -24,13 +24,13 @@ import com.google.gson.GsonBuilder;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.server.common.NonNullUpdatedValue;
import org.sonar.server.common.UpdatedValue;
import org.sonar.server.common.gitlab.config.GitlabConfiguration;
import org.sonar.server.common.gitlab.config.GitlabConfigurationService;
import org.sonar.server.common.gitlab.config.SynchronizationType;
import org.sonar.server.common.gitlab.config.UpdateGitlabConfigurationRequest;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule;
@@ -48,8 +48,8 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.sonar.server.common.gitlab.config.SynchronizationType.AUTO_PROVISIONING;
import static org.sonar.server.common.gitlab.config.SynchronizationType.JIT;
import static org.sonar.server.common.gitlab.config.ProvisioningType.AUTO_PROVISIONING;
import static org.sonar.server.common.gitlab.config.ProvisioningType.JIT;
import static org.sonar.server.v2.WebApiEndpoints.GITLAB_CONFIGURATION_ENDPOINT;
import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
@@ -79,9 +79,10 @@ public class DefaultGitlabConfigurationControllerTest {
GITLAB_CONFIGURATION.applicationId(),
GITLAB_CONFIGURATION.url(),
GITLAB_CONFIGURATION.synchronizeGroups(),
SynchronizationType.valueOf(GITLAB_CONFIGURATION.synchronizationType().name()),
org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType.valueOf(GITLAB_CONFIGURATION.provisioningType().name()),
GITLAB_CONFIGURATION.allowUsersToSignUp(),
List.of("provisioning-group1", "provisioning-group2"));
List.of("provisioning-group1", "provisioning-group2"),
"error-message");
private static final String EXPECTED_CONFIGURATION = """
{
"id": "existing-id",
@@ -89,12 +90,13 @@ public class DefaultGitlabConfigurationControllerTest {
"applicationId": "application-id",
"url": "www.url.com",
"synchronizeGroups": true,
"synchronizationType": "AUTO_PROVISIONING",
"provisioningType": "AUTO_PROVISIONING",
"allowUsersToSignUp": true,
"provisioningGroups": [
"provisioning-group2",
"provisioning-group1"
]
],
"errorMessage": "error-message"
}
""";

@@ -103,6 +105,11 @@ public class DefaultGitlabConfigurationControllerTest {
private final GitlabConfigurationService gitlabConfigurationService = mock();
private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultGitlabConfigurationController(userSession, gitlabConfigurationService));

@Before
public void setUp() {
when(gitlabConfigurationService.validate(any())).thenReturn(Optional.of("error-message"));
}

@Test
public void fetchConfiguration_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
userSession.logIn().setNonSystemAdministrator();
@@ -193,7 +200,7 @@ public class DefaultGitlabConfigurationControllerTest {
"url": "www.url.com",
"secret": "newSecret",
"synchronizeGroups": true,
"synchronizationType": "AUTO_PROVISIONING",
"provisioningType": "AUTO_PROVISIONING",
"allowUsersToSignUp": true,
"provisioningToken": "token",
"provisioningGroups": [
@@ -231,7 +238,7 @@ public class DefaultGitlabConfigurationControllerTest {
String payload = """
{
"enabled": false,
"synchronizationType": "JIT",
"provisioningType": "JIT",
"allowUsersToSignUp": false,
"provisioningToken": null
}
@@ -271,7 +278,7 @@ public class DefaultGitlabConfigurationControllerTest {
"url": "www.url.com",
"secret": "123",
"synchronizeGroups": true,
"synchronizationType": "AUTO_PROVISIONING",
"provisioningType": "AUTO_PROVISIONING",
"allowUsersToSignUp": true,
"provisioningGroups": [
"provisioning-group2",
@@ -299,7 +306,7 @@ public class DefaultGitlabConfigurationControllerTest {
"secret": "123",
"url": "www.url.com",
"synchronizeGroups": true,
"synchronizationType": "AUTO_PROVISIONING",
"provisioningType": "AUTO_PROVISIONING",
"allowUsersToSignUp": true,
"provisioningGroups": [
"provisioning-group2",
@@ -317,7 +324,7 @@ public class DefaultGitlabConfigurationControllerTest {
"applicationId": "application-id",
"url": "www.url.com",
"synchronizeGroups": true,
"synchronizationType": "AUTO_PROVISIONING",
"provisioningType": "AUTO_PROVISIONING",
"allowUsersToSignUp": true,
"provisioningGroups": [
"provisioning-group2",
@@ -342,7 +349,7 @@ public class DefaultGitlabConfigurationControllerTest {
"secret": "123",
"url": "www.url.com",
"synchronizeGroups": true,
"synchronizationType": "AUTO_PROVISIONING"
"provisioningType": "AUTO_PROVISIONING"
}

"""))
@@ -355,7 +362,7 @@ public class DefaultGitlabConfigurationControllerTest {
"applicationId": "application-id",
"url": "www.url.com",
"synchronizeGroups": true,
"synchronizationType": "AUTO_PROVISIONING",
"provisioningType": "AUTO_PROVISIONING",
"allowUsersToSignUp": true,
"provisioningGroups": [
"provisioning-group2",
@@ -379,7 +386,7 @@ public class DefaultGitlabConfigurationControllerTest {
"applicationId": "application-id",
"url": "www.url.com",
"synchronizeGroups": true,
"synchronizationType": "AUTO_PROVISIONING",
"provisioningType": "AUTO_PROVISIONING",
"allowUsersToSignUp": true,
"provisioningGroups": [
"provisioning-group2",

+ 2
- 0
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java View File

@@ -82,6 +82,7 @@ import org.sonar.server.branch.ws.BranchWsModule;
import org.sonar.server.ce.CeModule;
import org.sonar.server.ce.projectdump.ProjectExportWsModule;
import org.sonar.server.ce.ws.CeWsModule;
import org.sonar.server.common.gitlab.config.GitlabConfigurationService;
import org.sonar.server.common.group.service.GroupMembershipService;
import org.sonar.server.common.group.service.GroupService;
import org.sonar.server.common.rule.service.RuleService;
@@ -401,6 +402,7 @@ public class PlatformLevel4 extends PlatformLevel {
new LdapModule(),
new SamlModule(),
new SamlValidationModule(),
GitlabConfigurationService.class,
GroupService.class,
GroupMembershipService.class,
DefaultAdminCredentialsVerifierImpl.class,

Loading…
Cancel
Save