@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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')) | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -35,7 +35,7 @@ public record GitlabConfiguration( | |||
boolean synchronizeGroups, | |||
SynchronizationType synchronizationType, | |||
ProvisioningType provisioningType, | |||
boolean allowUsersToSignUp, | |||
@@ -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; | |||
} | |||
} |
@@ -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 | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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) { |
@@ -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, |
@@ -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") |
@@ -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 | |||
) { | |||
} | |||
@@ -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 | |||
} |
@@ -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); |
@@ -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", |
@@ -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, |