.orElse(false);
}
+ @Override
+ public void queueSynchronisationTask() {
+ findManagedInstanceService()
+ .ifPresent(ManagedInstanceService::queueSynchronisationTask);
+ }
+
private Optional<ManagedInstanceService> findManagedInstanceService() {
Set<ManagedInstanceService> managedInstanceServices = delegates.stream()
.filter(ManagedInstanceService::isInstanceExternallyManaged)
boolean isGroupManaged(DbSession dbSession, String groupUuid);
+ void queueSynchronisationTask();
+
}
true));
}
+ @Test
+ public void queueSynchronisationTask_whenManagedNoInstanceServices_doesNotFail() {
+ assertThatNoException().isThrownBy(NO_MANAGED_SERVICES::queueSynchronisationTask);
+ }
+
+ @Test
+ public void queueSynchronisationTask_whenManagedInstanceServices_shouldDelegatesToRightService() {
+ NeverManagedInstanceService neverManagedInstanceService = spy(new NeverManagedInstanceService());
+ AlwaysManagedInstanceService alwaysManagedInstanceService = spy(new AlwaysManagedInstanceService());
+ Set<ManagedInstanceService> delegates = Set.of(neverManagedInstanceService, alwaysManagedInstanceService);
+ DelegatingManagedServices managedInstanceService = new DelegatingManagedServices(delegates);
+
+ managedInstanceService.queueSynchronisationTask();
+ verify(neverManagedInstanceService, never()).queueSynchronisationTask();
+ verify(alwaysManagedInstanceService).queueSynchronisationTask();
+ }
+
private ManagedInstanceService getManagedInstanceService(Set<String> userUuids, Map<String, Boolean> uuidToManaged) {
ManagedInstanceService anotherManagedInstanceService = mock(ManagedInstanceService.class);
when(anotherManagedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
return false;
}
+ @Override
+ public void queueSynchronisationTask() {
+
+ }
+
@Override
public Map<String, Boolean> getProjectUuidToManaged(DbSession dbSession, Set<String> projectUuids) {
return null;
return true;
}
+ @Override
+ public void queueSynchronisationTask() {
+
+ }
+
@Override
public Map<String, Boolean> getProjectUuidToManaged(DbSession dbSession, Set<String> projectUuids) {
return null;
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')
- compileOnlyApi 'com.google.code.findbugs:jsr305'
+ compileOnlyApi 'com.google.code.findbugs:jsr305'
compileOnlyApi 'javax.servlet:javax.servlet-api'
testImplementation 'org.apache.logging.log4j:log4j-api'
--- /dev/null
+/*
+ * 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;
+
+import com.google.common.base.Strings;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.auth.gitlab.GitLabIdentityProvider;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.ExternalGroupDto;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.management.ManagedInstanceService;
+
+import static org.assertj.core.api.Assertions.assertThat;
+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.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.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;
+import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_ENABLED;
+import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_GROUPS;
+import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_TOKEN;
+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.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.UpdateGitlabConfigurationRequest.builder;
+
+@RunWith(MockitoJUnitRunner.class)
+public class GitlabConfigurationServiceIT {
+
+ @Rule
+ public DbTester dbTester = DbTester.create();
+
+ @Mock
+ private ManagedInstanceService managedInstanceService;
+
+ private GitlabConfigurationService gitlabConfigurationService;
+
+ @Before
+ public void setUp() {
+ when(managedInstanceService.getProviderName()).thenReturn("gitlab");
+ gitlabConfigurationService = new GitlabConfigurationService(
+ managedInstanceService,
+ dbTester.getDbClient());
+ }
+
+ @Test
+ public void getConfiguration_whenIdIsNotGitlabConfiguration_throwsException() {
+ assertThatExceptionOfType(NotFoundException.class)
+ .isThrownBy(() -> gitlabConfigurationService.getConfiguration("not-gitlab-configuration"))
+ .withMessage("Gitlab configuration with id not-gitlab-configuration not found");
+ }
+
+ @Test
+ public void getConfiguration_whenNoConfiguration_throwsNotFoundException() {
+ assertThatThrownBy(() -> gitlabConfigurationService.getConfiguration("gitlab-configuration"))
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("GitLab configuration doesn't exist.");
+
+ }
+
+ @Test
+ public void getConfiguration_whenConfigurationSet_returnsConfig() {
+ gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
+
+ GitlabConfiguration configuration = gitlabConfigurationService.getConfiguration("gitlab-configuration");
+
+ assertConfigurationFields(configuration);
+ }
+
+ @Test
+ public void getConfiguration_whenConfigurationSetAndEmpty_returnsConfig() {
+ dbTester.properties().insertProperty(GITLAB_AUTH_ENABLED, "true", null);
+ dbTester.properties().insertProperty(GITLAB_AUTH_PROVISIONING_GROUPS, "", null);
+
+ GitlabConfiguration configuration = gitlabConfigurationService.getConfiguration("gitlab-configuration");
+
+ assertThat(configuration.id()).isEqualTo("gitlab-configuration");
+ assertThat(configuration.enabled()).isTrue();
+ assertThat(configuration.applicationId()).isEmpty();
+ assertThat(configuration.url()).isEmpty();
+ assertThat(configuration.secret()).isEmpty();
+ assertThat(configuration.synchronizeGroups()).isFalse();
+ assertThat(configuration.synchronizationType()).isEqualTo(SynchronizationType.JIT);
+ assertThat(configuration.allowUsersToSignUp()).isFalse();
+ assertThat(configuration.provisioningToken()).isNull();
+ assertThat(configuration.provisioningGroups()).isEmpty();
+ }
+
+ @Test
+ public void updateConfiguration_whenIdIsNotGitlabConfiguration_throwsException() {
+ gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
+ UpdateGitlabConfigurationRequest updateGitlabConfigurationRequest = builder().gitlabConfigurationId("not-gitlab-configuration").build();
+ assertThatExceptionOfType(NotFoundException.class)
+ .isThrownBy(() -> gitlabConfigurationService.updateConfiguration(updateGitlabConfigurationRequest))
+ .withMessage("Gitlab configuration with id not-gitlab-configuration not found");
+ }
+
+ @Test
+ public void updateConfiguration_whenConfigurationDoesntExist_throwsException() {
+ UpdateGitlabConfigurationRequest updateGitlabConfigurationRequest = builder().gitlabConfigurationId("gitlab-configuration").build();
+ assertThatExceptionOfType(NotFoundException.class)
+ .isThrownBy(() -> gitlabConfigurationService.updateConfiguration(updateGitlabConfigurationRequest))
+ .withMessage("GitLab configuration doesn't exist.");
+ }
+
+ @Test
+ public void updateConfiguration_whenAllUpdateFieldDefined_updatesEverything() {
+ gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.JIT));
+
+ UpdateGitlabConfigurationRequest updateRequest = builder()
+ .gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
+ .enabled(withValueOrThrow(true))
+ .applicationId(withValueOrThrow("applicationId"))
+ .url(withValueOrThrow("url"))
+ .secret(withValueOrThrow("secret"))
+ .synchronizeGroups(withValueOrThrow(true))
+ .synchronizationType(withValueOrThrow(SynchronizationType.AUTO_PROVISIONING))
+ .allowUserToSignUp(withValueOrThrow(true))
+ .provisioningToken(withValueOrThrow("provisioningToken"))
+ .provisioningGroups(withValueOrThrow(new LinkedHashSet<>(List.of("group1", "group2", "group3"))))
+ .build();
+
+ GitlabConfiguration gitlabConfiguration = gitlabConfigurationService.updateConfiguration(updateRequest);
+
+ verifySettingWasSet(GITLAB_AUTH_ENABLED, "true");
+ verifySettingWasSet(GITLAB_AUTH_APPLICATION_ID, "applicationId");
+ verifySettingWasSet(GITLAB_AUTH_URL, "url");
+ verifySettingWasSet(GITLAB_AUTH_SECRET, "secret");
+ verifySettingWasSet(GITLAB_AUTH_SYNC_USER_GROUPS, "true");
+ verifySettingWasSet(GITLAB_AUTH_PROVISIONING_ENABLED, "true");
+ verifySettingWasSet(GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP, "true");
+ verifySettingWasSet(GITLAB_AUTH_PROVISIONING_TOKEN, "provisioningToken");
+ verifySettingWasSet(GITLAB_AUTH_PROVISIONING_GROUPS, "group1,group2,group3");
+ verify(managedInstanceService).queueSynchronisationTask();
+
+ assertConfigurationFields(gitlabConfiguration);
+ }
+
+ @Test
+ public void updateConfiguration_whenAllUpdateFieldDefinedAndSetToFalse_updatesEverything() {
+ gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
+ verify(managedInstanceService).queueSynchronisationTask();
+ clearInvocations(managedInstanceService);
+
+ UpdateGitlabConfigurationRequest updateRequest = builder()
+ .gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
+ .enabled(withValueOrThrow(false))
+ .synchronizeGroups(withValueOrThrow(false))
+ .synchronizationType(withValueOrThrow(SynchronizationType.JIT))
+ .allowUserToSignUp(withValueOrThrow(false))
+ .build();
+
+ gitlabConfigurationService.updateConfiguration(updateRequest);
+
+ verifySettingWasSet(GITLAB_AUTH_ENABLED, "false");
+ verifySettingWasSet(GITLAB_AUTH_SYNC_USER_GROUPS, "false");
+ verifySettingWasSet(GITLAB_AUTH_PROVISIONING_ENABLED, "false");
+ verifySettingWasSet(GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP, "false");
+ verifyNoMoreInteractions(managedInstanceService);
+
+ }
+
+ @Test
+ public void updateConfiguration_whenSwitchingFromAutoToJit_shouldNotScheduleSyncAndCallManagedInstanceChecker() {
+ DbSession dbSession = dbTester.getSession();
+ 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));
+ verify(managedInstanceService).queueSynchronisationTask();
+ reset(managedInstanceService);
+
+ UpdateGitlabConfigurationRequest updateRequest = builder()
+ .gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
+ .provisioningToken(withValue(null))
+ .synchronizationType(withValueOrThrow(SynchronizationType.JIT))
+ .build();
+
+ gitlabConfigurationService.updateConfiguration(updateRequest);
+
+ verifyNoMoreInteractions(managedInstanceService);
+ assertThat(dbTester.getDbClient().externalGroupDao().selectByIdentityProvider(dbTester.getSession(), GitLabIdentityProvider.KEY)).isEmpty();
+ }
+
+ @Test
+ public void updateConfiguration_whenSwitchingToAutoProvisioningAndTheConfigIsNotEnabled_shouldThrow() {
+ gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.JIT));
+
+ UpdateGitlabConfigurationRequest disableRequest = builder()
+ .gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
+ .enabled(withValueOrThrow(false))
+ .build();
+
+ gitlabConfigurationService.updateConfiguration(disableRequest);
+
+ UpdateGitlabConfigurationRequest updateRequest = builder()
+ .gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
+ .synchronizationType(withValueOrThrow(SynchronizationType.AUTO_PROVISIONING))
+ .build();
+
+ assertThatThrownBy(() -> gitlabConfigurationService.updateConfiguration(updateRequest))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("GitLab authentication must be turned on to enable GitLab provisioning.");
+ verify(managedInstanceService, times(0)).queueSynchronisationTask();
+ }
+
+ @Test
+ public void updateConfiguration_whenSwitchingToAutoProvisioningAndProvisioningTokenIsNotDefined_shouldThrow() {
+ gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.JIT));
+
+ UpdateGitlabConfigurationRequest removeTokenRequest = builder()
+ .gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
+ .provisioningToken(withValue(null))
+ .build();
+
+ gitlabConfigurationService.updateConfiguration(removeTokenRequest);
+
+ UpdateGitlabConfigurationRequest updateRequest = builder()
+ .gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID)
+ .synchronizationType(withValueOrThrow(SynchronizationType.AUTO_PROVISIONING))
+ .build();
+
+ assertThatThrownBy(() -> gitlabConfigurationService.updateConfiguration(updateRequest))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Provisioning token must be set to enable GitLab provisioning.");
+ verify(managedInstanceService, times(0)).queueSynchronisationTask();
+ }
+
+ private static void assertConfigurationFields(GitlabConfiguration configuration) {
+ assertThat(configuration).isNotNull();
+ assertThat(configuration.id()).isEqualTo("gitlab-configuration");
+ assertThat(configuration.enabled()).isTrue();
+ assertThat(configuration.applicationId()).isEqualTo("applicationId");
+ assertThat(configuration.url()).isEqualTo("url");
+ assertThat(configuration.secret()).isEqualTo("secret");
+ assertThat(configuration.synchronizeGroups()).isTrue();
+ assertThat(configuration.synchronizationType()).isEqualTo(SynchronizationType.AUTO_PROVISIONING);
+ assertThat(configuration.allowUsersToSignUp()).isTrue();
+ assertThat(configuration.provisioningToken()).isEqualTo("provisioningToken");
+ assertThat(configuration.provisioningGroups()).containsExactlyInAnyOrder("group1", "group2", "group3");
+ }
+
+ @Test
+ public void createConfiguration_whenConfigurationAlreadyExists_shouldThrow() {
+ GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
+ gitlabConfigurationService.createConfiguration(gitlabConfiguration);
+
+ assertThatThrownBy(() -> gitlabConfigurationService.createConfiguration(gitlabConfiguration))
+ .isInstanceOf(BadRequestException.class)
+ .hasMessage("GitLab configuration already exists. Only one Gitlab configuration is supported.");
+ }
+
+ @Test
+ public void createConfiguration_whenAutoProvisioning_shouldCreateCorrectConfigurationAndScheduleSync() {
+ GitlabConfiguration configuration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
+
+ GitlabConfiguration createdConfiguration = gitlabConfigurationService.createConfiguration(configuration);
+
+ assertThat(createdConfiguration).isEqualTo(configuration);
+
+ verifyCommonSettings(configuration);
+
+ verify(managedInstanceService).queueSynchronisationTask();
+
+ }
+
+ @Test
+ public void createConfiguration_whenAutoProvisioningConfigIsIncorrect_shouldThrow() {
+ GitlabConfiguration configuration = new GitlabConfiguration(
+ UNIQUE_GITLAB_CONFIGURATION_ID,
+ true,
+ "applicationId",
+ "url",
+ "secret",
+ true,
+ SynchronizationType.AUTO_PROVISIONING,
+ true,
+ null,
+ Set.of("group1", "group2", "group3"));
+
+ assertThatThrownBy(() -> gitlabConfigurationService.createConfiguration(configuration))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Provisioning token must be set to enable GitLab provisioning.");
+
+ }
+
+ @Test
+ public void createConfiguration_whenInstanceIsExternallyManaged_shouldThrow() {
+ GitlabConfiguration configuration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
+
+ when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
+ when(managedInstanceService.getProviderName()).thenReturn("not-gitlab");
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> gitlabConfigurationService.createConfiguration(configuration))
+ .withMessage("It is not possible to synchronize SonarQube using GitLab, as it is already managed by not-gitlab.");
+
+ }
+
+ @Test
+ public void createConfiguration_whenJitProvisioning_shouldCreateCorrectConfiguration() {
+ GitlabConfiguration configuration = buildGitlabConfiguration(SynchronizationType.JIT);
+
+ GitlabConfiguration createdConfiguration = gitlabConfigurationService.createConfiguration(configuration);
+
+ assertThat(createdConfiguration).isEqualTo(configuration);
+
+ verifyCommonSettings(configuration);
+ verifyNoInteractions(managedInstanceService);
+
+ }
+
+ @Test
+ public void createConfiguration_whenJitProvisioningAndProvisioningTokenNotSet_shouldCreateCorrectConfiguration() {
+ GitlabConfiguration configuration = new GitlabConfiguration(
+ UNIQUE_GITLAB_CONFIGURATION_ID,
+ true,
+ "applicationId",
+ "url",
+ "secret",
+ true,
+ SynchronizationType.JIT,
+ true,
+ null,
+ Set.of("group1", "group2", "group3"));
+
+ GitlabConfiguration createdConfiguration = gitlabConfigurationService.createConfiguration(configuration);
+
+ assertThat(createdConfiguration).isEqualTo(configuration);
+
+ verifyCommonSettings(configuration);
+ verifyNoInteractions(managedInstanceService);
+
+ }
+
+ private void verifyCommonSettings(GitlabConfiguration configuration) {
+ verifySettingWasSet(GITLAB_AUTH_ENABLED, String.valueOf(configuration.enabled()));
+ verifySettingWasSet(GITLAB_AUTH_APPLICATION_ID, configuration.applicationId());
+ verifySettingWasSet(GITLAB_AUTH_URL, configuration.url());
+ verifySettingWasSet(GITLAB_AUTH_SECRET, configuration.secret());
+ verifySettingWasSet(GITLAB_AUTH_SYNC_USER_GROUPS, String.valueOf(configuration.synchronizeGroups()));
+ verifySettingWasSet(GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP, String.valueOf(configuration.allowUsersToSignUp()));
+ 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)));
+ }
+
+ private void verifySettingWasSet(String setting, @Nullable String value) {
+ assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty(setting).getValue()).isEqualTo(value);
+ }
+
+ @Test
+ public void deleteConfiguration_whenIdIsNotGitlabConfiguration_throwsException() {
+ assertThatThrownBy(() -> gitlabConfigurationService.deleteConfiguration("not-gitlab-configuration"))
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("Gitlab configuration with id not-gitlab-configuration not found");
+ }
+
+ @Test
+ public void deleteConfiguration_whenConfigurationDoesntExist_throwsException() {
+ assertThatThrownBy(() -> gitlabConfigurationService.deleteConfiguration("gitlab-configuration"))
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("GitLab configuration doesn't exist.");
+ }
+
+ @Test
+ public void deleteConfiguration_whenConfigurationExists_shouldDeleteConfiguration() {
+ DbSession dbSession = dbTester.getSession();
+ 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.deleteConfiguration("gitlab-configuration");
+
+ assertPropertyIsDeleted(GITLAB_AUTH_ENABLED);
+ assertPropertyIsDeleted(GITLAB_AUTH_APPLICATION_ID);
+ assertPropertyIsDeleted(GITLAB_AUTH_URL);
+ assertPropertyIsDeleted(GITLAB_AUTH_SECRET);
+ assertPropertyIsDeleted(GITLAB_AUTH_SYNC_USER_GROUPS);
+ assertPropertyIsDeleted(GITLAB_AUTH_PROVISIONING_ENABLED);
+ assertPropertyIsDeleted(GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP);
+ assertPropertyIsDeleted(GITLAB_AUTH_PROVISIONING_TOKEN);
+ assertPropertyIsDeleted(GITLAB_AUTH_PROVISIONING_GROUPS);
+
+ assertThat(dbTester.getDbClient().externalGroupDao().selectByIdentityProvider(dbTester.getSession(), GitLabIdentityProvider.KEY)).isEmpty();
+ }
+
+ private void assertPropertyIsDeleted(String property) {
+ assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty(property)).isNull();
+ }
+
+ @Test
+ public void triggerRun_whenConfigIsCorrect_shouldTriggerSync() {
+ gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING));
+ reset(managedInstanceService);
+
+ gitlabConfigurationService.triggerRun();
+
+ verify(managedInstanceService).queueSynchronisationTask();
+ }
+
+ @Test
+ public void triggerRun_whenConfigIsForJit_shouldThrow() {
+ gitlabConfigurationService.createConfiguration(buildGitlabConfiguration(SynchronizationType.JIT));
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> gitlabConfigurationService.triggerRun())
+ .withMessage("Auto provisioning must be activated");
+ }
+
+ @Test
+ public void triggerRun_whenConfigIsDisabled_shouldThrow() {
+ GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
+ gitlabConfigurationService.createConfiguration(gitlabConfiguration);
+ gitlabConfigurationService.updateConfiguration(builder().gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID).enabled(withValueOrThrow(false)).build());
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> gitlabConfigurationService.triggerRun())
+ .withMessage("GitLab authentication must be turned on to enable GitLab provisioning.");
+ }
+
+ @Test
+ public void triggerRun_whenProvisioningTokenIsNotSet_shouldThrow() {
+ GitlabConfiguration gitlabConfiguration = buildGitlabConfiguration(SynchronizationType.AUTO_PROVISIONING);
+ gitlabConfigurationService.createConfiguration(gitlabConfiguration);
+ gitlabConfigurationService.updateConfiguration(builder().gitlabConfigurationId(UNIQUE_GITLAB_CONFIGURATION_ID).provisioningToken(withValue(null)).build());
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> gitlabConfigurationService.triggerRun())
+ .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"));
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.util.Set;
+import javax.annotation.Nullable;
+
+public record GitlabConfiguration(
+ String id,
+
+ boolean enabled,
+
+ String applicationId,
+
+ String url,
+
+ String secret,
+
+ boolean synchronizeGroups,
+
+ SynchronizationType synchronizationType,
+
+ boolean allowUsersToSignUp,
+
+ @Nullable
+ String provisioningToken,
+
+ Set<String> provisioningGroups
+) {
+}
--- /dev/null
+/*
+ * 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;
+
+import com.google.common.base.Strings;
+import java.util.Arrays;
+import java.util.List;
+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.auth.gitlab.GitLabIdentityProvider;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.common.UpdatedValue;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.management.ManagedInstanceService;
+
+import static java.lang.String.format;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+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;
+import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_ENABLED;
+import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_ENABLED;
+import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_GROUPS;
+import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_TOKEN;
+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.exceptions.NotFoundException.checkFound;
+
+public class GitlabConfigurationService {
+
+ private static final List<String> GITLAB_CONFIGURATION_PROPERTIES = List.of(
+ GITLAB_AUTH_ENABLED,
+ GITLAB_AUTH_APPLICATION_ID,
+ GITLAB_AUTH_URL,
+ GITLAB_AUTH_SECRET,
+ GITLAB_AUTH_SYNC_USER_GROUPS,
+ GITLAB_AUTH_PROVISIONING_ENABLED,
+ GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP,
+ GITLAB_AUTH_PROVISIONING_TOKEN,
+ GITLAB_AUTH_PROVISIONING_GROUPS);
+
+ public static final String UNIQUE_GITLAB_CONFIGURATION_ID = "gitlab-configuration";
+ private final ManagedInstanceService managedInstanceService;
+ private final DbClient dbClient;
+
+ public GitlabConfigurationService(ManagedInstanceService managedInstanceService, DbClient dbClient) {
+ this.managedInstanceService = managedInstanceService;
+ this.dbClient = dbClient;
+ }
+
+ public GitlabConfiguration updateConfiguration(UpdateGitlabConfigurationRequest updateRequest) {
+ UpdatedValue<Boolean> provisioningEnabled =
+ updateRequest.synchronizationType().map(GitlabConfigurationService::shouldEnableAutoProvisioning);
+ try (DbSession dbSession = dbClient.openSession(true)) {
+ throwIfConfigurationDoesntExist(dbSession);
+ GitlabConfiguration currentConfiguration = getConfiguration(updateRequest.gitlabConfigurationId(), dbSession);
+ setIfDefined(dbSession, GITLAB_AUTH_ENABLED, updateRequest.enabled().map(String::valueOf));
+ setIfDefined(dbSession, GITLAB_AUTH_APPLICATION_ID, updateRequest.applicationId());
+ setIfDefined(dbSession, GITLAB_AUTH_URL, updateRequest.url());
+ setIfDefined(dbSession, GITLAB_AUTH_SECRET, updateRequest.secret());
+ setIfDefined(dbSession, GITLAB_AUTH_SYNC_USER_GROUPS, updateRequest.synchronizeGroups().map(String::valueOf));
+ setIfDefined(dbSession, GITLAB_AUTH_PROVISIONING_ENABLED, provisioningEnabled.map(String::valueOf));
+ setIfDefined(dbSession, GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP, updateRequest.allowUsersToSignUp().map(String::valueOf));
+ 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());
+ GitlabConfiguration updatedConfiguration = getConfiguration(UNIQUE_GITLAB_CONFIGURATION_ID, dbSession);
+ if (shouldTriggerProvisioning) {
+ triggerRun(updatedConfiguration);
+ }
+ dbSession.commit();
+ return updatedConfiguration;
+ }
+ }
+
+ private void setIfDefined(DbSession dbSession, String propertyName, UpdatedValue<String> value) {
+ value
+ .map(definedValue -> new PropertyDto().setKey(propertyName).setValue(definedValue))
+ .applyIfDefined(property -> dbClient.propertiesDao().saveProperty(dbSession, property));
+ }
+
+ 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);
+ if (disableAutoProvisioning) {
+ dbClient.externalGroupDao().deleteByExternalIdentityProvider(dbSession, GitLabIdentityProvider.KEY);
+ }
+ }
+
+ public GitlabConfiguration getConfiguration(String id) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ throwIfNotUniqueConfigurationId(id);
+ throwIfConfigurationDoesntExist(dbSession);
+ return getConfiguration(id, dbSession);
+ }
+ }
+
+ public Optional<GitlabConfiguration> findConfigurations() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ if (dbClient.propertiesDao().selectGlobalProperty(dbSession, GITLAB_AUTH_ENABLED) == null) {
+ return Optional.empty();
+ }
+ return Optional.of(getConfiguration(UNIQUE_GITLAB_CONFIGURATION_ID, dbSession));
+ }
+ }
+
+ private Boolean getBooleanOrFalse(DbSession dbSession, String property) {
+ return Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(dbSession, property))
+ .map(dto -> Boolean.valueOf(dto.getValue())).orElse(false);
+ }
+
+ private String getStringPropertyOrEmpty(DbSession dbSession, String property) {
+ return Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(dbSession, property))
+ .map(PropertyDto::getValue).orElse("");
+ }
+
+ private String getStringPropertyOrNull(DbSession dbSession, String property) {
+ return Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(dbSession, property))
+ .map(dto -> Strings.emptyToNull(dto.getValue())).orElse(null);
+ }
+
+ private static void throwIfNotUniqueConfigurationId(String id) {
+ if (!UNIQUE_GITLAB_CONFIGURATION_ID.equals(id)) {
+ throw new NotFoundException(format("Gitlab configuration with id %s not found", id));
+ }
+ }
+
+ public void deleteConfiguration(String id) {
+ throwIfNotUniqueConfigurationId(id);
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ throwIfConfigurationDoesntExist(dbSession);
+ GITLAB_CONFIGURATION_PROPERTIES.forEach(property -> dbClient.propertiesDao().deleteGlobalProperty(property, dbSession));
+ dbClient.externalGroupDao().deleteByExternalIdentityProvider(dbSession, GitLabIdentityProvider.KEY);
+ dbSession.commit();
+ }
+ }
+
+ private void throwIfConfigurationDoesntExist(DbSession dbSession) {
+ 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;
+ }
+
+ public GitlabConfiguration createConfiguration(GitlabConfiguration configuration) {
+ throwIfConfigurationAlreadyExists();
+
+ boolean enableAutoProvisioning = shouldEnableAutoProvisioning(configuration.synchronizationType());
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ setProperty(dbSession, GITLAB_AUTH_ENABLED, String.valueOf(configuration.enabled()));
+ setProperty(dbSession, GITLAB_AUTH_APPLICATION_ID, configuration.applicationId());
+ setProperty(dbSession, GITLAB_AUTH_URL, configuration.url());
+ setProperty(dbSession, GITLAB_AUTH_SECRET, configuration.secret());
+ setProperty(dbSession, GITLAB_AUTH_SYNC_USER_GROUPS, String.valueOf(configuration.synchronizeGroups()));
+ setProperty(dbSession, GITLAB_AUTH_PROVISIONING_ENABLED, String.valueOf(enableAutoProvisioning));
+ setProperty(dbSession, GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP, String.valueOf(configuration.allowUsersToSignUp()));
+ setProperty(dbSession, GITLAB_AUTH_PROVISIONING_TOKEN, configuration.provisioningToken());
+ setProperty(dbSession, GITLAB_AUTH_PROVISIONING_GROUPS, String.join(",", configuration.provisioningGroups()));
+ if (enableAutoProvisioning) {
+ triggerRun(configuration);
+ }
+ GitlabConfiguration createdConfiguration = getConfiguration(UNIQUE_GITLAB_CONFIGURATION_ID, dbSession);
+ dbSession.commit();
+ return createdConfiguration;
+ }
+
+ }
+
+ private void throwIfConfigurationAlreadyExists() {
+ Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(GITLAB_AUTH_ENABLED)).ifPresent(property -> {
+ throw BadRequestException.create("GitLab configuration already exists. Only one Gitlab configuration is supported.");
+ });
+ }
+
+ private static boolean shouldEnableAutoProvisioning(SynchronizationType synchronizationType) {
+ return AUTO_PROVISIONING.equals(synchronizationType);
+ }
+
+ private void setProperty(DbSession dbSession, String propertyName, @Nullable String value) {
+ dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(propertyName).setValue(value));
+ }
+
+ private GitlabConfiguration getConfiguration(String id, DbSession dbSession) {
+ throwIfNotUniqueConfigurationId(id);
+ throwIfConfigurationDoesntExist(dbSession);
+ return new GitlabConfiguration(
+ UNIQUE_GITLAB_CONFIGURATION_ID,
+ getBooleanOrFalse(dbSession, GITLAB_AUTH_ENABLED),
+ getStringPropertyOrEmpty(dbSession, GITLAB_AUTH_APPLICATION_ID),
+ getStringPropertyOrEmpty(dbSession, GITLAB_AUTH_URL),
+ getStringPropertyOrEmpty(dbSession, GITLAB_AUTH_SECRET),
+ getBooleanOrFalse(dbSession, GITLAB_AUTH_SYNC_USER_GROUPS),
+ toSynchronizationType(getBooleanOrFalse(dbSession, GITLAB_AUTH_PROVISIONING_ENABLED)),
+ getBooleanOrFalse(dbSession, GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP),
+ getStringPropertyOrNull(dbSession, GITLAB_AUTH_PROVISIONING_TOKEN),
+ getProvisioningGroups(dbSession)
+ );
+ }
+
+ private Set<String> getProvisioningGroups(DbSession dbSession) {
+ return Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(dbSession, GITLAB_AUTH_PROVISIONING_GROUPS))
+ .map(dto -> Arrays.stream(dto.getValue().split(","))
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.toSet())
+ ).orElse(Set.of());
+ }
+
+ public void triggerRun() {
+ GitlabConfiguration configuration = getConfiguration(UNIQUE_GITLAB_CONFIGURATION_ID);
+ triggerRun(configuration);
+ }
+
+ 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(isNotBlank(configuration.provisioningToken()), getErrorMessage("Provisioning token must be set"));
+ }
+
+ private void checkInstanceNotManagedByAnotherProvider() {
+ if (managedInstanceService.isInstanceExternallyManaged()) {
+ Optional.of(managedInstanceService.getProviderName()).filter(providerName -> !"gitlab".equals(providerName))
+ .ifPresent(providerName -> {
+ throw new IllegalStateException("It is not possible to synchronize SonarQube using GitLab, as it is already managed by "
+ + providerName + ".");
+ });
+ }
+ }
+
+ private static String getErrorMessage(String prefix) {
+ return format("%s to enable GitLab provisioning.", prefix);
+ }
+}
--- /dev/null
+/*
+ * 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 SynchronizationType {
+ JIT,
+ AUTO_PROVISIONING
+}
--- /dev/null
+/*
+ * 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;
+
+import java.util.Set;
+import org.sonar.server.common.NonNullUpdatedValue;
+import org.sonar.server.common.UpdatedValue;
+
+public record UpdateGitlabConfigurationRequest(
+ String gitlabConfigurationId,
+ NonNullUpdatedValue<Boolean> enabled,
+ NonNullUpdatedValue<String> applicationId,
+ NonNullUpdatedValue<String> url,
+ NonNullUpdatedValue<String> secret,
+ NonNullUpdatedValue<Boolean> synchronizeGroups,
+ NonNullUpdatedValue<SynchronizationType> synchronizationType,
+ NonNullUpdatedValue<Boolean> allowUsersToSignUp,
+ UpdatedValue<String> provisioningToken,
+ NonNullUpdatedValue<Set<String>> provisioningGroups
+) {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+ private String gitlabConfigurationId;
+ private NonNullUpdatedValue<Boolean> enabled = NonNullUpdatedValue.undefined();
+ private NonNullUpdatedValue<String> applicationId = NonNullUpdatedValue.undefined();
+ 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<Boolean> allowUserToSignUp = NonNullUpdatedValue.undefined();
+ private UpdatedValue<String> provisioningToken = UpdatedValue.undefined();
+ private NonNullUpdatedValue<Set<String>> provisioningGroups = NonNullUpdatedValue.undefined();
+
+ private Builder() {
+ }
+
+ public Builder gitlabConfigurationId(String gitlabConfigurationId) {
+ this.gitlabConfigurationId = gitlabConfigurationId;
+ return this;
+ }
+
+ public Builder enabled(NonNullUpdatedValue<Boolean> enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+
+ public Builder applicationId(NonNullUpdatedValue<String> applicationId) {
+ this.applicationId = applicationId;
+ return this;
+ }
+
+ public Builder url(NonNullUpdatedValue<String> url) {
+ this.url = url;
+ return this;
+ }
+
+ public Builder secret(NonNullUpdatedValue<String> secret) {
+ this.secret = secret;
+ return this;
+ }
+
+ public Builder synchronizeGroups(NonNullUpdatedValue<Boolean> synchronizeGroups) {
+ this.synchronizeGroups = synchronizeGroups;
+ return this;
+ }
+
+ public Builder synchronizationType(NonNullUpdatedValue<SynchronizationType> synchronizationType) {
+ this.synchronizationType = synchronizationType;
+ return this;
+ }
+
+ public Builder allowUserToSignUp(NonNullUpdatedValue<Boolean> allowUserToSignUp) {
+ this.allowUserToSignUp = allowUserToSignUp;
+ return this;
+ }
+
+ public Builder provisioningToken(UpdatedValue<String> provisioningToken) {
+ this.provisioningToken = provisioningToken;
+ return this;
+ }
+
+ public Builder provisioningGroups(NonNullUpdatedValue<Set<String>> provisioningGroups) {
+ this.provisioningGroups = provisioningGroups;
+ return this;
+ }
+
+ public UpdateGitlabConfigurationRequest build() {
+ return new UpdateGitlabConfigurationRequest(gitlabConfigurationId, enabled, applicationId, url, secret, synchronizeGroups, synchronizationType, allowUserToSignUp,
+ provisioningToken, provisioningGroups);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.common.gitlab.config;
+
+import javax.annotation.ParametersAreNonnullByDefault;
public static final String CLEAN_CODE_POLICY_DOMAIN = "/clean-code-policy";
public static final String RULES_ENDPOINT = CLEAN_CODE_POLICY_DOMAIN + "/rules";
+ public static final String GITLAB_CONFIGURATION_ENDPOINT = DOP_TRANSLATION_DOMAIN + "/gitlab-configurations";
+
public static final String INTERNAL = "internal";
private WebApiEndpoints() {
--- /dev/null
+/*
+ * 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.controller;
+
+import java.util.HashSet;
+import java.util.List;
+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.UpdateGitlabConfigurationRequest;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.v2.api.gitlab.config.request.GitlabConfigurationCreateRestRequest;
+import org.sonar.server.v2.api.gitlab.config.request.GitlabConfigurationUpdateRestRequest;
+import org.sonar.server.v2.api.gitlab.config.resource.GitlabConfigurationResource;
+import org.sonar.server.v2.api.gitlab.config.response.GitlabConfigurationSearchRestResponse;
+import org.sonar.server.v2.api.response.PageRestResponse;
+
+import static org.sonar.server.common.gitlab.config.GitlabConfigurationService.UNIQUE_GITLAB_CONFIGURATION_ID;
+
+public class DefaultGitlabConfigurationController implements GitlabConfigurationController {
+
+ private final UserSession userSession;
+ private final GitlabConfigurationService gitlabConfigurationService;
+
+ public DefaultGitlabConfigurationController(UserSession userSession, GitlabConfigurationService gitlabConfigurationService) {
+ this.userSession = userSession;
+ this.gitlabConfigurationService = gitlabConfigurationService;
+ }
+
+ @Override
+ public GitlabConfigurationResource getGitlabConfiguration(String id) {
+ userSession.checkIsSystemAdministrator();
+ return getGitlabConfigurationResource(id);
+ }
+
+ @Override
+ public GitlabConfigurationSearchRestResponse searchGitlabConfiguration() {
+ userSession.checkIsSystemAdministrator();
+
+ List<GitlabConfigurationResource> gitlabConfigurationResources = gitlabConfigurationService.findConfigurations()
+ .stream()
+ .map(DefaultGitlabConfigurationController::toGitLabConfigurationResource)
+ .toList();
+
+ PageRestResponse pageRestResponse = new PageRestResponse(1, 1000, gitlabConfigurationResources.size());
+ return new GitlabConfigurationSearchRestResponse(gitlabConfigurationResources, pageRestResponse);
+ }
+
+ @Override
+ public GitlabConfigurationResource create(GitlabConfigurationCreateRestRequest createRequest) {
+ userSession.checkIsSystemAdministrator();
+ GitlabConfiguration createdConfiguration = gitlabConfigurationService.createConfiguration(toGitlabConfiguration(createRequest));
+ return toGitLabConfigurationResource(createdConfiguration);
+ }
+
+
+ private static GitlabConfiguration toGitlabConfiguration(GitlabConfigurationCreateRestRequest createRestRequest) {
+ return new GitlabConfiguration(
+ UNIQUE_GITLAB_CONFIGURATION_ID,
+ createRestRequest.enabled(),
+ createRestRequest.applicationId(),
+ createRestRequest.url(),
+ createRestRequest.secret(),
+ createRestRequest.synchronizeGroups(),
+ toSynchronizationType(createRestRequest.synchronizationType()),
+ createRestRequest.allowUsersToSignUp() != null && createRestRequest.allowUsersToSignUp(),
+ createRestRequest.provisioningToken(),
+ createRestRequest.provisioningGroups() == null ? Set.of() : Set.copyOf(createRestRequest.provisioningGroups()));
+ }
+
+ private GitlabConfigurationResource getGitlabConfigurationResource(String id) {
+ return toGitLabConfigurationResource(gitlabConfigurationService.getConfiguration(id));
+ }
+
+ @Override
+ public GitlabConfigurationResource updateGitlabConfiguration(String id, GitlabConfigurationUpdateRestRequest updateRequest) {
+ userSession.checkIsSystemAdministrator();
+ UpdateGitlabConfigurationRequest updateGitlabConfigurationRequest = toUpdateGitlabConfigurationRequest(id, updateRequest);
+ return toGitLabConfigurationResource(gitlabConfigurationService.updateConfiguration(updateGitlabConfigurationRequest));
+ }
+
+ private static UpdateGitlabConfigurationRequest toUpdateGitlabConfigurationRequest(String id,
+ GitlabConfigurationUpdateRestRequest updateRequest) {
+ return UpdateGitlabConfigurationRequest.builder()
+ .gitlabConfigurationId(id)
+ .enabled(updateRequest.getEnabled().toNonNullUpdatedValue())
+ .applicationId(updateRequest.getApplicationId().toNonNullUpdatedValue())
+ .url(updateRequest.getUrl().toNonNullUpdatedValue())
+ .secret(updateRequest.getSecret().toNonNullUpdatedValue())
+ .synchronizeGroups(updateRequest.getSynchronizeGroups().toNonNullUpdatedValue())
+ .synchronizationType(updateRequest.getSynchronizationType().map(DefaultGitlabConfigurationController::toSynchronizationType).toNonNullUpdatedValue())
+ .allowUserToSignUp(updateRequest.getAllowUsersToSignUp().toNonNullUpdatedValue())
+ .provisioningToken(updateRequest.getProvisioningToken().toUpdatedValue())
+ .provisioningGroups(updateRequest.getProvisioningGroups().map(DefaultGitlabConfigurationController::getGroups).toNonNullUpdatedValue())
+ .build();
+ }
+
+ private static Set<String> getGroups(List<String> groups) {
+ return new HashSet<>(groups);
+ }
+
+ private static GitlabConfigurationResource toGitLabConfigurationResource(GitlabConfiguration configuration) {
+ return new GitlabConfigurationResource(
+ configuration.id(),
+ configuration.enabled(),
+ configuration.applicationId(),
+ configuration.url(),
+ configuration.synchronizeGroups(),
+ toRestSynchronizationType(configuration),
+ configuration.allowUsersToSignUp(),
+ sortGroups(configuration.provisioningGroups()));
+ }
+
+ private static SynchronizationType toRestSynchronizationType(GitlabConfiguration configuration) {
+ return SynchronizationType.valueOf(configuration.synchronizationType().name());
+ }
+
+ private static SynchronizationType toSynchronizationType(SynchronizationType synchronizationType) {
+ return SynchronizationType.valueOf(synchronizationType.name());
+ }
+
+ private static List<String> sortGroups(Set<String> groups) {
+ return groups.stream().sorted().toList();
+ }
+
+ @Override
+ public void deleteGitlabConfiguration(String id) {
+ userSession.checkIsSystemAdministrator();
+ gitlabConfigurationService.deleteConfiguration(id);
+ }
+}
--- /dev/null
+/*
+ * 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.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.extensions.Extension;
+import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
+import javax.validation.Valid;
+import org.sonar.server.v2.api.gitlab.config.request.GitlabConfigurationCreateRestRequest;
+import org.sonar.server.v2.api.gitlab.config.request.GitlabConfigurationUpdateRestRequest;
+import org.sonar.server.v2.api.gitlab.config.resource.GitlabConfigurationResource;
+import org.sonar.server.v2.api.gitlab.config.response.GitlabConfigurationSearchRestResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import static org.sonar.server.v2.WebApiEndpoints.GITLAB_CONFIGURATION_ENDPOINT;
+import static org.sonar.server.v2.WebApiEndpoints.INTERNAL;
+import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE;
+
+@RequestMapping(GITLAB_CONFIGURATION_ENDPOINT)
+@RestController
+public interface GitlabConfigurationController {
+
+ @GetMapping(path = "/{id}")
+ @ResponseStatus(HttpStatus.OK)
+ @Operation(summary = "Fetch a GitLab configuration", description = """
+ Fetch a GitLab configuration. Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ GitlabConfigurationResource getGitlabConfiguration(
+ @PathVariable("id") @Parameter(description = "The id of the configuration to fetch.", required = true, in = ParameterIn.PATH) String id);
+
+ @GetMapping
+ @Operation(summary = "Search GitLab configs", description = """
+ Get the list of GitLab configurations.
+ Note that a single configuration is supported at this time.
+ Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ GitlabConfigurationSearchRestResponse searchGitlabConfiguration();
+
+ @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(HttpStatus.OK)
+ @Operation(summary = "Update a Gitlab configuration", description = """
+ Update a Gitlab configuration. Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ GitlabConfigurationResource updateGitlabConfiguration(@PathVariable("id") String id, @Valid @RequestBody GitlabConfigurationUpdateRestRequest updateRequest);
+
+ @PostMapping
+ @Operation(summary = "Create Gitlab configuration", description = """
+ Create a new Gitlab configuration.
+ Note that only a single configuration can exist at a time.
+ Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ GitlabConfigurationResource create(@Valid @RequestBody GitlabConfigurationCreateRestRequest createRequest);
+
+ @DeleteMapping(path = "/{id}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @Operation(summary = "Delete a GitLab configuration", description = """
+ Delete a GitLab configuration.
+ Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ void deleteGitlabConfiguration(
+ @PathVariable("id") @Parameter(description = "The id of the configuration to delete.", required = true, in = ParameterIn.PATH) String id);
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.v2.api.gitlab.config.controller;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * 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.request;
+
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Schema;
+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;
+
+public record GitlabConfigurationCreateRestRequest(
+
+ @NotNull
+ @Schema(description = "Enable Gitlab authentication")
+ boolean enabled,
+
+ @NotEmpty
+ @Schema(description = "Gitlab Application id")
+ String applicationId,
+ @NotEmpty
+ @Schema(description = "Url of Gitlab instance for authentication (for instance https://gitlab.com)")
+ String url,
+ @NotEmpty
+ @Schema(accessMode = Schema.AccessMode.WRITE_ONLY, description = "Secret of the application")
+ String secret,
+
+ @NotNull
+ @Schema(description = "Set whether to synchronize groups")
+ Boolean synchronizeGroups,
+
+ @NotNull
+ @Schema(description = "Type of synchronization")
+ SynchronizationType synchronizationType,
+ @Nullable
+ @Schema(accessMode = Schema.AccessMode.WRITE_ONLY, description = "Gitlab token for provisioning")
+ String provisioningToken,
+
+ @Schema(description = "Allow user to sign up")
+ @Nullable
+ Boolean allowUsersToSignUp,
+
+ @ArraySchema(arraySchema = @Schema(description = "Root GitLab groups to provision."))
+ @Nullable
+ List<String> provisioningGroups
+) {
+}
--- /dev/null
+/*
+ * 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.request;
+
+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.common.model.UpdateField;
+
+public class GitlabConfigurationUpdateRestRequest {
+
+ private UpdateField<Boolean> enabled = UpdateField.undefined();
+ private UpdateField<String> applicationId = UpdateField.undefined();
+ 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<Boolean> allowUsersToSignUp = UpdateField.undefined();
+ private UpdateField<String> provisioningToken = UpdateField.undefined();
+ private UpdateField<List<String>> provisioningGroups = UpdateField.undefined();
+
+ @Schema(implementation = Boolean.class, description = "Enable Gitlab authentication")
+ public UpdateField<Boolean> getEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(Boolean enabled) {
+ this.enabled = UpdateField.withValue(enabled);
+ }
+
+ @Schema(implementation = String.class, description = "Gitlab Application id")
+ public UpdateField<String> getApplicationId() {
+ return applicationId;
+ }
+
+ public void setApplicationId(String applicationId) {
+ this.applicationId = UpdateField.withValue(applicationId);
+ }
+
+ @Schema(implementation = String.class, description = "Url of Gitlab instance for authentication (for instance https://gitlab.com/api/v4)")
+ public UpdateField<String> getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = UpdateField.withValue(url);
+ }
+
+ @Schema(implementation = String.class, description = "Secret of the application", nullable = true)
+ public UpdateField<String> getSecret() {
+ return secret;
+ }
+
+ public void setSecret(String secret) {
+ this.secret = UpdateField.withValue(secret);
+ }
+
+ @Schema(implementation = Boolean.class, description = "Set whether to synchronize groups")
+ public UpdateField<Boolean> getSynchronizeGroups() {
+ return synchronizeGroups;
+ }
+
+ public void setSynchronizeGroups(Boolean synchronizeGroups) {
+ this.synchronizeGroups = UpdateField.withValue(synchronizeGroups);
+ }
+
+ @Schema(implementation = SynchronizationType.class, description = "Type of synchronization")
+ public UpdateField<SynchronizationType> getSynchronizationType() {
+ return synchronizationType;
+ }
+
+ public void setSynchronizationType(SynchronizationType synchronizationType) {
+ this.synchronizationType = UpdateField.withValue(synchronizationType);
+ }
+
+ @Schema(implementation = Boolean.class, description = "Allow user to sign up")
+ public UpdateField<Boolean> getAllowUsersToSignUp() {
+ return allowUsersToSignUp;
+ }
+
+ public void setAllowUsersToSignUp(Boolean allowUsersToSignUp) {
+ this.allowUsersToSignUp = UpdateField.withValue(allowUsersToSignUp);
+ }
+
+ @Size(min = 1)
+ @Schema(implementation = String.class, description = "Gitlab token for provisioning", nullable = true)
+ public UpdateField<String> getProvisioningToken() {
+ return provisioningToken;
+ }
+
+ public void setProvisioningToken(String provisioningToken) {
+ this.provisioningToken = UpdateField.withValue(provisioningToken);
+ }
+
+ @ArraySchema(arraySchema = @Schema(description = "Root gitlab groups to provision."), schema = @Schema(implementation = String.class))
+ public UpdateField<List<String>> getProvisioningGroups() {
+ return provisioningGroups;
+ }
+
+ public void setProvisioningGroups(List<String> provisioningGroups) {
+ this.provisioningGroups = UpdateField.withValue(provisioningGroups);
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.v2.api.gitlab.config.request;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * 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;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.List;
+import org.sonar.server.common.gitlab.config.SynchronizationType;
+
+public record GitlabConfigurationResource(
+
+ @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+ String id,
+
+ boolean enabled,
+
+ @Schema(implementation = String.class, description = "Gitlab Application id")
+ String applicationId,
+
+ @Schema(description = "Url of Gitlab instance for authentication (for instance https://gitlab.com/api/v4)")
+ String url,
+
+ boolean synchronizeGroups,
+
+ SynchronizationType synchronizationType,
+
+ boolean allowUsersToSignUp,
+
+ @Schema(description = "Root Gitlab groups to provision")
+ List<String> provisioningGroups
+) {
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.v2.api.gitlab.config.resource;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * 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.response;
+
+import java.util.List;
+import org.sonar.server.v2.api.gitlab.config.resource.GitlabConfigurationResource;
+import org.sonar.server.v2.api.response.PageRestResponse;
+
+public record GitlabConfigurationSearchRestResponse(List<GitlabConfigurationResource> gitlabConfigurations, PageRestResponse page) {}
+
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.v2.api.gitlab.config.response;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.Nullable;
import org.sonar.api.resources.Languages;
import org.sonar.db.DbClient;
+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.health.CeStatusNodeCheck;
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;
import org.sonar.server.user.UserSession;
+import org.sonar.server.v2.api.gitlab.config.controller.DefaultGitlabConfigurationController;
+import org.sonar.server.v2.api.gitlab.config.controller.GitlabConfigurationController;
import org.sonar.server.v2.api.group.controller.DefaultGroupController;
import org.sonar.server.v2.api.group.controller.GroupController;
import org.sonar.server.v2.api.membership.controller.DefaultGroupMembershipController;
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);
+ }
+
}
--- /dev/null
+/*
+ * 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;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+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;
+import org.sonar.server.v2.api.ControllerTester;
+import org.sonar.server.v2.api.gitlab.config.controller.DefaultGitlabConfigurationController;
+import org.sonar.server.v2.api.gitlab.config.resource.GitlabConfigurationResource;
+import org.sonar.server.v2.api.gitlab.config.response.GitlabConfigurationSearchRestResponse;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+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.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;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+public class DefaultGitlabConfigurationControllerTest {
+ private static final Gson GSON = new GsonBuilder().create();
+
+ private static final GitlabConfiguration GITLAB_CONFIGURATION = new GitlabConfiguration(
+ "existing-id",
+ true,
+ "application-id",
+ "www.url.com",
+ "secret",
+ true,
+ AUTO_PROVISIONING,
+ true,
+ "provisioning-token",
+ Set.of("provisioning-group2", "provisioning-group1"));
+ private static final GitlabConfigurationResource EXPECTED_GITLAB_CONF_RESOURCE = new GitlabConfigurationResource(
+ GITLAB_CONFIGURATION.id(),
+ GITLAB_CONFIGURATION.enabled(),
+ GITLAB_CONFIGURATION.applicationId(),
+ GITLAB_CONFIGURATION.url(),
+ GITLAB_CONFIGURATION.synchronizeGroups(),
+ SynchronizationType.valueOf(GITLAB_CONFIGURATION.synchronizationType().name()),
+ GITLAB_CONFIGURATION.allowUsersToSignUp(),
+ List.of("provisioning-group1", "provisioning-group2"));
+ private static final String EXPECTED_CONFIGURATION = """
+ {
+ "id": "existing-id",
+ "enabled": true,
+ "applicationId": "application-id",
+ "url": "www.url.com",
+ "synchronizeGroups": true,
+ "synchronizationType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "provisioningGroups": [
+ "provisioning-group2",
+ "provisioning-group1"
+ ]
+ }
+ """;
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+ private final GitlabConfigurationService gitlabConfigurationService = mock();
+ private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultGitlabConfigurationController(userSession, gitlabConfigurationService));
+
+ @Test
+ public void fetchConfiguration_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
+ userSession.logIn().setNonSystemAdministrator();
+
+ mockMvc.perform(get(GITLAB_CONFIGURATION_ENDPOINT + "/1"))
+ .andExpectAll(
+ status().isForbidden(),
+ content().json("{\"message\":\"Insufficient privileges\"}"));
+ }
+
+ @Test
+ public void fetchConfiguration_whenConfigNotFound_throws() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(gitlabConfigurationService.getConfiguration("not-existing")).thenThrow(new NotFoundException("bla"));
+
+ mockMvc.perform(get(GITLAB_CONFIGURATION_ENDPOINT + "/not-existing"))
+ .andExpectAll(
+ status().isNotFound(),
+ content().json("{\"message\":\"bla\"}"));
+ }
+
+ @Test
+ public void fetchConfiguration_whenConfigFound_returnsIt() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(gitlabConfigurationService.getConfiguration("existing-id")).thenReturn(GITLAB_CONFIGURATION);
+
+ mockMvc.perform(get(GITLAB_CONFIGURATION_ENDPOINT + "/existing-id"))
+ .andExpectAll(
+ status().isOk(),
+ content().json(EXPECTED_CONFIGURATION));
+ }
+
+ @Test
+ public void search_whenNoParameters_shouldUseDefaultAndForwardToGroupMembershipService() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(gitlabConfigurationService.findConfigurations()).thenReturn(Optional.of(GITLAB_CONFIGURATION));
+
+ MvcResult mvcResult = mockMvc.perform(get(GITLAB_CONFIGURATION_ENDPOINT))
+ .andExpect(status().isOk())
+ .andReturn();
+
+ GitlabConfigurationSearchRestResponse gitlabConfigurationResource = GSON.fromJson(mvcResult.getResponse().getContentAsString(), GitlabConfigurationSearchRestResponse.class);
+
+ assertThat(gitlabConfigurationResource.page().pageSize()).isEqualTo(1000);
+ assertThat(gitlabConfigurationResource.page().pageIndex()).isEqualTo(1);
+ assertThat(gitlabConfigurationResource.page().total()).isEqualTo(1);
+ assertThat(gitlabConfigurationResource.gitlabConfigurations()).containsExactly(EXPECTED_GITLAB_CONF_RESOURCE);
+ }
+
+ @Test
+ public void search_whenNoParametersAndNoConfig_shouldReturnEmptyList() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(gitlabConfigurationService.findConfigurations()).thenReturn(Optional.empty());
+
+ MvcResult mvcResult = mockMvc.perform(get(GITLAB_CONFIGURATION_ENDPOINT))
+ .andExpect(status().isOk())
+ .andReturn();
+
+ GitlabConfigurationSearchRestResponse gitlabConfigurationResource = GSON.fromJson(mvcResult.getResponse().getContentAsString(), GitlabConfigurationSearchRestResponse.class);
+
+ assertThat(gitlabConfigurationResource.page().pageSize()).isEqualTo(1000);
+ assertThat(gitlabConfigurationResource.page().pageIndex()).isEqualTo(1);
+ assertThat(gitlabConfigurationResource.page().total()).isZero();
+ assertThat(gitlabConfigurationResource.gitlabConfigurations()).isEmpty();
+ }
+
+ @Test
+ public void updateConfiguration_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
+ userSession.logIn().setNonSystemAdministrator();
+
+ mockMvc.perform(patch(GITLAB_CONFIGURATION_ENDPOINT + "/existing-id")
+ .contentType(JSON_MERGE_PATCH_CONTENT_TYPE)
+ .content("{}"))
+ .andExpectAll(
+ status().isForbidden(),
+ content().json("{\"message\":\"Insufficient privileges\"}"));
+ }
+
+ @Test
+ public void updateConfiguration_whenAllFieldsUpdated_performUpdates() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(gitlabConfigurationService.updateConfiguration(any())).thenReturn(GITLAB_CONFIGURATION);
+
+ String payload = """
+ {
+ "enabled": true,
+ "applicationId": "application-id",
+ "url": "www.url.com",
+ "secret": "newSecret",
+ "synchronizeGroups": true,
+ "synchronizationType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "provisioningToken": "token",
+ "provisioningGroups": [
+ "provisioning-group2",
+ "provisioning-group1"
+ ]
+ }
+ """;
+
+ mockMvc.perform(patch(GITLAB_CONFIGURATION_ENDPOINT + "/existing-id")
+ .contentType(JSON_MERGE_PATCH_CONTENT_TYPE)
+ .content(payload))
+ .andExpectAll(
+ status().isOk(),
+ content().json(EXPECTED_CONFIGURATION));
+
+ verify(gitlabConfigurationService).updateConfiguration(new UpdateGitlabConfigurationRequest(
+ "existing-id",
+ NonNullUpdatedValue.withValueOrThrow(true),
+ NonNullUpdatedValue.withValueOrThrow("application-id"),
+ NonNullUpdatedValue.withValueOrThrow("www.url.com"),
+ NonNullUpdatedValue.withValueOrThrow("newSecret"),
+ NonNullUpdatedValue.withValueOrThrow(true),
+ NonNullUpdatedValue.withValueOrThrow(AUTO_PROVISIONING),
+ NonNullUpdatedValue.withValueOrThrow(true),
+ UpdatedValue.withValue("token"),
+ NonNullUpdatedValue.withValueOrThrow(Set.of("provisioning-group2", "provisioning-group1"))));
+ }
+
+ @Test
+ public void updateConfiguration_whenSomeFieldsUpdated_performUpdates() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(gitlabConfigurationService.updateConfiguration(any())).thenReturn(GITLAB_CONFIGURATION);
+
+ String payload = """
+ {
+ "enabled": false,
+ "synchronizationType": "JIT",
+ "allowUsersToSignUp": false,
+ "provisioningToken": null
+ }
+ """;
+
+ mockMvc.perform(patch(GITLAB_CONFIGURATION_ENDPOINT + "/existing-id")
+ .contentType(JSON_MERGE_PATCH_CONTENT_TYPE)
+ .content(payload))
+ .andExpectAll(
+ status().isOk(),
+ content().json(EXPECTED_CONFIGURATION));
+
+ verify(gitlabConfigurationService).updateConfiguration(new UpdateGitlabConfigurationRequest(
+ "existing-id",
+ NonNullUpdatedValue.withValueOrThrow(false),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.withValueOrThrow(JIT),
+ NonNullUpdatedValue.withValueOrThrow(false),
+ UpdatedValue.withValue(null),
+ NonNullUpdatedValue.undefined()));
+ }
+
+ @Test
+ public void create_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
+ userSession.logIn().setNonSystemAdministrator();
+
+ mockMvc.perform(
+ post(GITLAB_CONFIGURATION_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("""
+ {
+ "enabled": true,
+ "applicationId": "application-id",
+ "url": "www.url.com",
+ "secret": "123",
+ "synchronizeGroups": true,
+ "synchronizationType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "provisioningGroups": [
+ "provisioning-group2",
+ "provisioning-group1"
+ ]
+ }
+ """))
+ .andExpectAll(
+ status().isForbidden(),
+ content().json("{\"message\":\"Insufficient privileges\"}"));
+ }
+
+ @Test
+ public void create_whenConfigCreated_returnsIt() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(gitlabConfigurationService.createConfiguration(any())).thenReturn(GITLAB_CONFIGURATION);
+
+ mockMvc.perform(
+ post(GITLAB_CONFIGURATION_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("""
+ {
+ "enabled": true,
+ "applicationId": "application-id",
+ "secret": "123",
+ "url": "www.url.com",
+ "synchronizeGroups": true,
+ "synchronizationType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "provisioningGroups": [
+ "provisioning-group2",
+ "provisioning-group1"
+ ]
+ }
+
+ """))
+ .andExpectAll(
+ status().isOk(),
+ content().json("""
+ {
+ "id": "existing-id",
+ "enabled": true,
+ "applicationId": "application-id",
+ "url": "www.url.com",
+ "synchronizeGroups": true,
+ "synchronizationType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "provisioningGroups": [
+ "provisioning-group2",
+ "provisioning-group1"
+ ]
+ }
+ """));
+
+ }
+ @Test
+ public void create_whenConfigCreatedWithoutOptionalParams_returnsIt() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(gitlabConfigurationService.createConfiguration(any())).thenReturn(GITLAB_CONFIGURATION);
+
+ mockMvc.perform(
+ post(GITLAB_CONFIGURATION_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("""
+ {
+ "enabled": true,
+ "applicationId": "application-id",
+ "secret": "123",
+ "url": "www.url.com",
+ "synchronizeGroups": true,
+ "synchronizationType": "AUTO_PROVISIONING"
+ }
+
+ """))
+ .andExpectAll(
+ status().isOk(),
+ content().json("""
+ {
+ "id": "existing-id",
+ "enabled": true,
+ "applicationId": "application-id",
+ "url": "www.url.com",
+ "synchronizeGroups": true,
+ "synchronizationType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "provisioningGroups": [
+ "provisioning-group2",
+ "provisioning-group1"
+ ]
+ }
+ """));
+
+ }
+
+ @Test
+ public void create_whenRequiredParameterIsMissing_shouldReturnBadRequest() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+
+ mockMvc.perform(
+ post(GITLAB_CONFIGURATION_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("""
+ {
+ "enabled": true,
+ "applicationId": "application-id",
+ "url": "www.url.com",
+ "synchronizeGroups": true,
+ "synchronizationType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "provisioningGroups": [
+ "provisioning-group2",
+ "provisioning-group1"
+ ]
+ }
+ """))
+ .andExpectAll(
+ status().isBadRequest(),
+ content().json(
+ "{\"message\":\"Value {} for field secret was rejected. Error: must not be empty.\"}"));
+
+ }
+
+ @Test
+ public void delete_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
+ userSession.logIn().setNonSystemAdministrator();
+
+ mockMvc.perform(
+ delete(GITLAB_CONFIGURATION_ENDPOINT + "/existing-id"))
+ .andExpectAll(
+ status().isForbidden(),
+ content().json("{\"message\":\"Insufficient privileges\"}"));
+ }
+
+ @Test
+ public void delete_whenConfigIsDeleted_returnsNoContent() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+
+ mockMvc.perform(
+ delete(GITLAB_CONFIGURATION_ENDPOINT + "/existing-id"))
+ .andExpectAll(
+ status().isNoContent());
+
+ verify(gitlabConfigurationService).deleteConfiguration("existing-id");
+ }
+
+ @Test
+ public void delete_whenConfigNotFound_returnsNotFound() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ doThrow(new NotFoundException("Not found")).when(gitlabConfigurationService).deleteConfiguration("not-existing");
+
+ mockMvc.perform(
+ delete(GITLAB_CONFIGURATION_ENDPOINT + "/not-existing"))
+ .andExpectAll(
+ status().isNotFound(),
+ content().json("{\"message\":\"Not found\"}"));
+ }
+
+}