]> source.dussan.org Git - sonarqube.git/blob
0f2fbaf88fd491e7f5ef2d9733f2105de24ebb61
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.common.gitlab.config;
21
22 import com.google.common.base.Strings;
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.Optional;
26 import java.util.Set;
27 import java.util.stream.Collectors;
28 import javax.annotation.Nullable;
29 import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator;
30 import org.sonar.api.server.ServerSide;
31 import org.sonar.auth.gitlab.GitLabIdentityProvider;
32 import org.sonar.db.DbClient;
33 import org.sonar.db.DbSession;
34 import org.sonar.db.property.PropertyDto;
35 import org.sonar.server.common.UpdatedValue;
36 import org.sonar.server.exceptions.BadRequestException;
37 import org.sonar.server.exceptions.NotFoundException;
38 import org.sonar.server.management.ManagedInstanceService;
39
40 import static java.lang.String.format;
41 import static org.apache.commons.lang.StringUtils.isNotBlank;
42 import static org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator.ValidationMode.AUTH_ONLY;
43 import static org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator.ValidationMode.COMPLETE;
44 import static org.sonar.api.utils.Preconditions.checkState;
45 import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP;
46 import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_APPLICATION_ID;
47 import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_ENABLED;
48 import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_ENABLED;
49 import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_GROUPS;
50 import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_PROVISIONING_TOKEN;
51 import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_SECRET;
52 import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_SYNC_USER_GROUPS;
53 import static org.sonar.auth.gitlab.GitLabSettings.GITLAB_AUTH_URL;
54 import static org.sonar.server.common.gitlab.config.ProvisioningType.AUTO_PROVISIONING;
55 import static org.sonar.server.common.gitlab.config.ProvisioningType.JIT;
56 import static org.sonar.server.exceptions.NotFoundException.checkFound;
57
58 @ServerSide
59 public class GitlabConfigurationService {
60
61   private static final List<String> GITLAB_CONFIGURATION_PROPERTIES = List.of(
62     GITLAB_AUTH_ENABLED,
63     GITLAB_AUTH_APPLICATION_ID,
64     GITLAB_AUTH_URL,
65     GITLAB_AUTH_SECRET,
66     GITLAB_AUTH_SYNC_USER_GROUPS,
67     GITLAB_AUTH_PROVISIONING_ENABLED,
68     GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP,
69     GITLAB_AUTH_PROVISIONING_TOKEN,
70     GITLAB_AUTH_PROVISIONING_GROUPS);
71
72   public static final String UNIQUE_GITLAB_CONFIGURATION_ID = "gitlab-configuration";
73   private final DbClient dbClient;
74   private final ManagedInstanceService managedInstanceService;
75   private final GitlabGlobalSettingsValidator gitlabGlobalSettingsValidator;
76
77   public GitlabConfigurationService(DbClient dbClient,
78     ManagedInstanceService managedInstanceService, GitlabGlobalSettingsValidator gitlabGlobalSettingsValidator) {
79     this.dbClient = dbClient;
80     this.managedInstanceService = managedInstanceService;
81     this.gitlabGlobalSettingsValidator = gitlabGlobalSettingsValidator;
82   }
83
84   public GitlabConfiguration updateConfiguration(UpdateGitlabConfigurationRequest updateRequest) {
85     UpdatedValue<Boolean> provisioningEnabled =
86       updateRequest.provisioningType().map(GitlabConfigurationService::shouldEnableAutoProvisioning);
87     try (DbSession dbSession = dbClient.openSession(true)) {
88       throwIfConfigurationDoesntExist(dbSession);
89       GitlabConfiguration currentConfiguration = getConfiguration(updateRequest.gitlabConfigurationId(), dbSession);
90       setIfDefined(dbSession, GITLAB_AUTH_ENABLED, updateRequest.enabled().map(String::valueOf));
91       setIfDefined(dbSession, GITLAB_AUTH_APPLICATION_ID, updateRequest.applicationId());
92       setIfDefined(dbSession, GITLAB_AUTH_URL, updateRequest.url());
93       setIfDefined(dbSession, GITLAB_AUTH_SECRET, updateRequest.secret());
94       setIfDefined(dbSession, GITLAB_AUTH_SYNC_USER_GROUPS, updateRequest.synchronizeGroups().map(String::valueOf));
95       setIfDefined(dbSession, GITLAB_AUTH_PROVISIONING_ENABLED, provisioningEnabled.map(String::valueOf));
96       setIfDefined(dbSession, GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP, updateRequest.allowUsersToSignUp().map(String::valueOf));
97       setIfDefined(dbSession, GITLAB_AUTH_PROVISIONING_TOKEN, updateRequest.provisioningToken());
98       setIfDefined(dbSession, GITLAB_AUTH_PROVISIONING_GROUPS, updateRequest.provisioningGroups().map(groups -> String.join(",", groups)));
99       boolean shouldTriggerProvisioning =
100         provisioningEnabled.orElse(false) && !currentConfiguration.provisioningType().equals(AUTO_PROVISIONING);
101       deleteExternalGroupsWhenDisablingAutoProvisioning(dbSession, currentConfiguration, updateRequest.provisioningType());
102       GitlabConfiguration updatedConfiguration = getConfiguration(UNIQUE_GITLAB_CONFIGURATION_ID, dbSession);
103       if (shouldTriggerProvisioning) {
104         triggerRun(updatedConfiguration);
105       }
106       dbSession.commit();
107       return updatedConfiguration;
108     }
109   }
110
111   private void setIfDefined(DbSession dbSession, String propertyName, UpdatedValue<String> value) {
112     value
113       .map(definedValue -> new PropertyDto().setKey(propertyName).setValue(definedValue))
114       .applyIfDefined(property -> dbClient.propertiesDao().saveProperty(dbSession, property));
115   }
116
117   private void deleteExternalGroupsWhenDisablingAutoProvisioning(
118     DbSession dbSession,
119     GitlabConfiguration currentConfiguration,
120     UpdatedValue<ProvisioningType> provisioningTypeFromUpdate) {
121     boolean disableAutoProvisioning =
122       provisioningTypeFromUpdate.map(provisioningType -> provisioningType.equals(JIT)).orElse(false)
123         && currentConfiguration.provisioningType().equals(AUTO_PROVISIONING);
124     if (disableAutoProvisioning) {
125       dbClient.externalGroupDao().deleteByExternalIdentityProvider(dbSession, GitLabIdentityProvider.KEY);
126     }
127   }
128
129   public GitlabConfiguration getConfiguration(String id) {
130     try (DbSession dbSession = dbClient.openSession(false)) {
131       throwIfNotUniqueConfigurationId(id);
132       throwIfConfigurationDoesntExist(dbSession);
133       return getConfiguration(id, dbSession);
134     }
135   }
136
137   public Optional<GitlabConfiguration> findConfigurations() {
138     try (DbSession dbSession = dbClient.openSession(false)) {
139       if (dbClient.propertiesDao().selectGlobalProperty(dbSession, GITLAB_AUTH_ENABLED) == null) {
140         return Optional.empty();
141       }
142       return Optional.of(getConfiguration(UNIQUE_GITLAB_CONFIGURATION_ID, dbSession));
143     }
144   }
145
146   private Boolean getBooleanOrFalse(DbSession dbSession, String property) {
147     return Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(dbSession, property))
148       .map(dto -> Boolean.valueOf(dto.getValue())).orElse(false);
149   }
150
151   private String getStringPropertyOrEmpty(DbSession dbSession, String property) {
152     return Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(dbSession, property))
153       .map(PropertyDto::getValue).orElse("");
154   }
155
156   private String getStringPropertyOrNull(DbSession dbSession, String property) {
157     return Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(dbSession, property))
158       .map(dto -> Strings.emptyToNull(dto.getValue())).orElse(null);
159   }
160
161   private static void throwIfNotUniqueConfigurationId(String id) {
162     if (!UNIQUE_GITLAB_CONFIGURATION_ID.equals(id)) {
163       throw new NotFoundException(format("Gitlab configuration with id %s not found", id));
164     }
165   }
166
167   public void deleteConfiguration(String id) {
168     throwIfNotUniqueConfigurationId(id);
169     try (DbSession dbSession = dbClient.openSession(false)) {
170       throwIfConfigurationDoesntExist(dbSession);
171       GITLAB_CONFIGURATION_PROPERTIES.forEach(property -> dbClient.propertiesDao().deleteGlobalProperty(property, dbSession));
172       dbClient.externalGroupDao().deleteByExternalIdentityProvider(dbSession, GitLabIdentityProvider.KEY);
173       dbSession.commit();
174     }
175   }
176
177   private void throwIfConfigurationDoesntExist(DbSession dbSession) {
178     checkFound(dbClient.propertiesDao().selectGlobalProperty(dbSession, GITLAB_AUTH_ENABLED), "GitLab configuration doesn't exist.");
179   }
180
181   private static ProvisioningType toProvisioningType(boolean provisioningEnabled) {
182     return provisioningEnabled ? AUTO_PROVISIONING : JIT;
183   }
184
185   public GitlabConfiguration createConfiguration(GitlabConfiguration configuration) {
186     throwIfConfigurationAlreadyExists();
187
188     boolean enableAutoProvisioning = shouldEnableAutoProvisioning(configuration.provisioningType());
189     try (DbSession dbSession = dbClient.openSession(false)) {
190       setProperty(dbSession, GITLAB_AUTH_ENABLED, String.valueOf(configuration.enabled()));
191       setProperty(dbSession, GITLAB_AUTH_APPLICATION_ID, configuration.applicationId());
192       setProperty(dbSession, GITLAB_AUTH_URL, configuration.url());
193       setProperty(dbSession, GITLAB_AUTH_SECRET, configuration.secret());
194       setProperty(dbSession, GITLAB_AUTH_SYNC_USER_GROUPS, String.valueOf(configuration.synchronizeGroups()));
195       setProperty(dbSession, GITLAB_AUTH_PROVISIONING_ENABLED, String.valueOf(enableAutoProvisioning));
196       setProperty(dbSession, GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP, String.valueOf(configuration.allowUsersToSignUp()));
197       setProperty(dbSession, GITLAB_AUTH_PROVISIONING_TOKEN, configuration.provisioningToken());
198       setProperty(dbSession, GITLAB_AUTH_PROVISIONING_GROUPS, String.join(",", configuration.provisioningGroups()));
199       if (enableAutoProvisioning) {
200         triggerRun(configuration);
201       }
202       GitlabConfiguration createdConfiguration = getConfiguration(UNIQUE_GITLAB_CONFIGURATION_ID, dbSession);
203       dbSession.commit();
204       return createdConfiguration;
205     }
206
207   }
208
209   private void throwIfConfigurationAlreadyExists() {
210     Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(GITLAB_AUTH_ENABLED)).ifPresent(property -> {
211       throw BadRequestException.create("GitLab configuration already exists. Only one Gitlab configuration is supported.");
212     });
213   }
214
215   private static boolean shouldEnableAutoProvisioning(ProvisioningType provisioningType) {
216     return AUTO_PROVISIONING.equals(provisioningType);
217   }
218
219   private void setProperty(DbSession dbSession, String propertyName, @Nullable String value) {
220     dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(propertyName).setValue(value));
221   }
222
223   private GitlabConfiguration getConfiguration(String id, DbSession dbSession) {
224     throwIfNotUniqueConfigurationId(id);
225     throwIfConfigurationDoesntExist(dbSession);
226     return new GitlabConfiguration(
227       UNIQUE_GITLAB_CONFIGURATION_ID,
228       getBooleanOrFalse(dbSession, GITLAB_AUTH_ENABLED),
229       getStringPropertyOrEmpty(dbSession, GITLAB_AUTH_APPLICATION_ID),
230       getStringPropertyOrEmpty(dbSession, GITLAB_AUTH_URL),
231       getStringPropertyOrEmpty(dbSession, GITLAB_AUTH_SECRET),
232       getBooleanOrFalse(dbSession, GITLAB_AUTH_SYNC_USER_GROUPS),
233       toProvisioningType(getBooleanOrFalse(dbSession, GITLAB_AUTH_PROVISIONING_ENABLED)),
234       getBooleanOrFalse(dbSession, GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP),
235       getStringPropertyOrNull(dbSession, GITLAB_AUTH_PROVISIONING_TOKEN),
236       getProvisioningGroups(dbSession)
237     );
238   }
239
240   private Set<String> getProvisioningGroups(DbSession dbSession) {
241     return Optional.ofNullable(dbClient.propertiesDao().selectGlobalProperty(dbSession, GITLAB_AUTH_PROVISIONING_GROUPS))
242       .map(dto -> Arrays.stream(dto.getValue().split(","))
243         .filter(s -> !s.isEmpty())
244         .collect(Collectors.toSet())
245       ).orElse(Set.of());
246   }
247
248   public void triggerRun() {
249     GitlabConfiguration configuration = getConfiguration(UNIQUE_GITLAB_CONFIGURATION_ID);
250     triggerRun(configuration);
251   }
252
253   private void triggerRun(GitlabConfiguration gitlabConfiguration) {
254     throwIfConfigIncompleteOrInstanceAlreadyManaged(gitlabConfiguration);
255     managedInstanceService.queueSynchronisationTask();
256   }
257
258   private void throwIfConfigIncompleteOrInstanceAlreadyManaged(GitlabConfiguration configuration) {
259     checkInstanceNotManagedByAnotherProvider();
260     checkState(AUTO_PROVISIONING.equals(configuration.provisioningType()), "Auto provisioning must be activated");
261     checkState(configuration.enabled(), getErrorMessage("GitLab authentication must be turned on"));
262     checkState(isNotBlank(configuration.provisioningToken()), getErrorMessage("Provisioning token must be set"));
263   }
264
265   private void checkInstanceNotManagedByAnotherProvider() {
266     if (managedInstanceService.isInstanceExternallyManaged()) {
267       Optional.of(managedInstanceService.getProviderName()).filter(providerName -> !"gitlab".equals(providerName))
268         .ifPresent(providerName -> {
269           throw new IllegalStateException("It is not possible to synchronize SonarQube using GitLab, as it is already managed by "
270             + providerName + ".");
271         });
272     }
273   }
274
275   private static String getErrorMessage(String prefix) {
276     return format("%s to enable GitLab provisioning.", prefix);
277   }
278
279   public Optional<String> validate(GitlabConfiguration gitlabConfiguration) {
280     if (!gitlabConfiguration.enabled()) {
281       return Optional.empty();
282     }
283     String url = (gitlabConfiguration.url() + "/api/v4").replace("//", "/");
284     try {
285       gitlabGlobalSettingsValidator.validate(
286         toValidationMode(gitlabConfiguration.provisioningType()),
287         url,
288         gitlabConfiguration.provisioningToken()
289       );
290     } catch (Exception e) {
291       return Optional.of(e.getMessage());
292     }
293     return Optional.empty();
294   }
295
296   private static GitlabGlobalSettingsValidator.ValidationMode toValidationMode(ProvisioningType provisioningType) {
297     return AUTO_PROVISIONING.equals(provisioningType) ? COMPLETE : AUTH_ONLY;
298   }
299 }