aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-webserver-webapi-v2/src
diff options
context:
space:
mode:
authorAntoine Vigneau <antoine.vigneau@sonarsource.com>2024-06-11 17:44:45 +0200
committersonartech <sonartech@sonarsource.com>2024-06-17 20:02:35 +0000
commit078306d53ad53ba38d5d4b06e6e8958a0c2c6595 (patch)
treecd0ac74f560aac0e2a3720b096239d7519f856c0 /server/sonar-webserver-webapi-v2/src
parent0bdfddeed0bf06255f61c6b59dcfc6d132598e14 (diff)
downloadsonarqube-078306d53ad53ba38d5d4b06e6e8958a0c2c6595.tar.gz
sonarqube-078306d53ad53ba38d5d4b06e6e8958a0c2c6595.zip
SONAR-22365 Fix SSF-571
Diffstat (limited to 'server/sonar-webserver-webapi-v2/src')
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java2
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/DefaultGithubConfigurationController.java164
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java97
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/package-info.java23
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/GithubConfigurationCreateRestRequest.java97
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/GithubConfigurationUpdateRestRequest.java160
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/package-info.java23
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/resource/GithubConfigurationResource.java61
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/resource/package-info.java23
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/response/GithubConfigurationSearchRestResponse.java27
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/response/package-info.java23
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/DefaultGitlabConfigurationController.java6
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationCreateRestRequest.java2
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationUpdateRestRequest.java2
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/GitlabConfigurationResource.java1
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/model/ProvisioningType.java (renamed from server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/ProvisioningType.java)2
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java10
-rw-r--r--server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/github/config/DefaultGithubConfigurationControllerTest.java499
-rw-r--r--server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/gitlab/config/DefaultGitlabConfigurationControllerTest.java3
19 files changed, 1217 insertions, 8 deletions
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
index a4e820e1be5..0bd6af1386b 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
@@ -39,6 +39,8 @@ public class WebApiEndpoints {
public static final String GITLAB_CONFIGURATION_ENDPOINT = DOP_TRANSLATION_DOMAIN + "/gitlab-configurations";
+ public static final String GITHUB_CONFIGURATION_ENDPOINT = DOP_TRANSLATION_DOMAIN + "/github-configurations";
+
public static final String BOUND_PROJECTS_ENDPOINT = DOP_TRANSLATION_DOMAIN + "/bound-projects";
public static final String PROJECT_BINDINGS_ENDPOINT = DOP_TRANSLATION_DOMAIN + "/project-bindings";
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/DefaultGithubConfigurationController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/DefaultGithubConfigurationController.java
new file mode 100644
index 00000000000..03a69639c34
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/DefaultGithubConfigurationController.java
@@ -0,0 +1,164 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.config.controller;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.sonar.server.common.github.config.GithubConfiguration;
+import org.sonar.server.common.github.config.GithubConfigurationService;
+import org.sonar.server.common.github.config.UpdateGithubConfigurationRequest;
+import org.sonar.server.common.gitlab.config.ProvisioningType;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.v2.api.github.config.request.GithubConfigurationCreateRestRequest;
+import org.sonar.server.v2.api.github.config.request.GithubConfigurationUpdateRestRequest;
+import org.sonar.server.v2.api.github.config.resource.GithubConfigurationResource;
+import org.sonar.server.v2.api.github.config.response.GithubConfigurationSearchRestResponse;
+import org.sonar.server.v2.api.response.PageRestResponse;
+
+import static org.sonar.api.utils.Preconditions.checkArgument;
+import static org.sonar.server.common.github.config.GithubConfigurationService.UNIQUE_GITHUB_CONFIGURATION_ID;
+
+public class DefaultGithubConfigurationController implements GithubConfigurationController {
+
+ private final UserSession userSession;
+ private final GithubConfigurationService githubConfigurationService;
+
+ public DefaultGithubConfigurationController(UserSession userSession, GithubConfigurationService githubConfigurationService) {
+ this.userSession = userSession;
+ this.githubConfigurationService = githubConfigurationService;
+ }
+
+ @Override
+ public GithubConfigurationResource getGithubConfiguration(String id) {
+ userSession.checkIsSystemAdministrator();
+ return getGithubConfigurationResource(id);
+ }
+
+ @Override
+ public GithubConfigurationSearchRestResponse searchGithubConfiguration() {
+ userSession.checkIsSystemAdministrator();
+
+ List<GithubConfigurationResource> githubConfigurationResources = githubConfigurationService.findConfigurations()
+ .stream()
+ .map(this::toGithubConfigurationResource)
+ .toList();
+
+ PageRestResponse pageRestResponse = new PageRestResponse(1, 1000, githubConfigurationResources.size());
+ return new GithubConfigurationSearchRestResponse(githubConfigurationResources, pageRestResponse);
+ }
+
+ @Override
+ public GithubConfigurationResource createGithubConfiguration(GithubConfigurationCreateRestRequest createRequest) {
+ userSession.checkIsSystemAdministrator();
+ GithubConfiguration createdConfiguration = githubConfigurationService.createConfiguration(toGithubConfiguration(createRequest));
+ return toGithubConfigurationResource(createdConfiguration);
+ }
+
+ private static GithubConfiguration toGithubConfiguration(GithubConfigurationCreateRestRequest createRestRequest) {
+ return new GithubConfiguration(
+ UNIQUE_GITHUB_CONFIGURATION_ID,
+ createRestRequest.enabled(),
+ createRestRequest.clientId(),
+ createRestRequest.clientSecret(),
+ createRestRequest.applicationId(),
+ createRestRequest.privateKey(),
+ createRestRequest.synchronizeGroups(),
+ createRestRequest.apiUrl(),
+ createRestRequest.webUrl(),
+ Set.copyOf(createRestRequest.allowedOrganizations()),
+ toProvisioningType(createRestRequest.provisioningType()),
+ createRestRequest.allowUsersToSignUp() != null && createRestRequest.allowUsersToSignUp(),
+ createRestRequest.projectVisibility() != null && createRestRequest.projectVisibility(),
+ createRestRequest.userConsentRequiredAfterUpgrade() != null && createRestRequest.userConsentRequiredAfterUpgrade());
+ }
+
+ private GithubConfigurationResource getGithubConfigurationResource(String id) {
+ return toGithubConfigurationResource(githubConfigurationService.getConfiguration(id));
+ }
+
+ @Override
+ public GithubConfigurationResource updateGithubConfiguration(String id, GithubConfigurationUpdateRestRequest updateRequest) {
+ userSession.checkIsSystemAdministrator();
+ UpdateGithubConfigurationRequest updateGithubConfigurationRequest = toUpdateGithubConfigurationRequest(id, updateRequest);
+ return toGithubConfigurationResource(githubConfigurationService.updateConfiguration(updateGithubConfigurationRequest));
+ }
+
+ private static UpdateGithubConfigurationRequest toUpdateGithubConfigurationRequest(String id, GithubConfigurationUpdateRestRequest updateRequest) {
+ return UpdateGithubConfigurationRequest.builder()
+ .githubConfigurationId(id)
+ .enabled(updateRequest.getEnabled().toNonNullUpdatedValue())
+ .clientId(updateRequest.getClientId().toNonNullUpdatedValue())
+ .clientSecret(updateRequest.getClientSecret().toNonNullUpdatedValue())
+ .applicationId(updateRequest.getApplicationId().toNonNullUpdatedValue())
+ .privateKey(updateRequest.getPrivateKey().toNonNullUpdatedValue())
+ .synchronizeGroups(updateRequest.getSynchronizeGroups().toNonNullUpdatedValue())
+ .apiUrl(updateRequest.getApiUrl().toNonNullUpdatedValue())
+ .webUrl(updateRequest.getWebUrl().toNonNullUpdatedValue())
+ .allowedOrganizations(updateRequest.getAllowedOrganizations().map(DefaultGithubConfigurationController::getOrganizations).toNonNullUpdatedValue())
+ .provisioningType(updateRequest.getProvisioningType().map(DefaultGithubConfigurationController::toProvisioningType).toNonNullUpdatedValue())
+ .allowUsersToSignUp(updateRequest.getAllowUsersToSignUp().toNonNullUpdatedValue())
+ .projectVisibility(updateRequest.getProjectVisibility().toNonNullUpdatedValue())
+ .userConsentRequiredAfterUpgrade(updateRequest.getUserConsentRequiredAfterUpgrade().toNonNullUpdatedValue())
+ .build();
+ }
+
+ private static Set<String> getOrganizations(@Nullable List<String> orgs) {
+ checkArgument(orgs != null, "allowedOrganizations must not be null");
+ return new HashSet<>(orgs);
+ }
+
+ private GithubConfigurationResource toGithubConfigurationResource(GithubConfiguration configuration) {
+ Optional<String> configurationError = githubConfigurationService.validate(configuration);
+ return new GithubConfigurationResource(
+ configuration.id(),
+ configuration.enabled(),
+ configuration.applicationId(),
+ configuration.synchronizeGroups(),
+ configuration.apiUrl(),
+ configuration.webUrl(),
+ sortGroups(configuration.allowedOrganizations()),
+ toRestProvisioningType(configuration),
+ configuration.allowUsersToSignUp(),
+ configuration.provisionProjectVisibility(),
+ configuration.userConsentRequiredAfterUpgrade(),
+ configurationError.orElse(null));
+ }
+
+ private static org.sonar.server.v2.api.model.ProvisioningType toRestProvisioningType(GithubConfiguration configuration) {
+ return org.sonar.server.v2.api.model.ProvisioningType.valueOf(configuration.provisioningType().name());
+ }
+
+ private static ProvisioningType toProvisioningType(org.sonar.server.v2.api.model.ProvisioningType provisioningType) {
+ return ProvisioningType.valueOf(provisioningType.name());
+ }
+
+ private static List<String> sortGroups(Set<String> groups) {
+ return groups.stream().sorted().toList();
+ }
+
+ @Override
+ public void deleteGithubConfiguration(String id) {
+ userSession.checkIsSystemAdministrator();
+ githubConfigurationService.deleteConfiguration(id);
+ }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java
new file mode 100644
index 00000000000..eb6865db0ee
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java
@@ -0,0 +1,97 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.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.github.config.request.GithubConfigurationCreateRestRequest;
+import org.sonar.server.v2.api.github.config.request.GithubConfigurationUpdateRestRequest;
+import org.sonar.server.v2.api.github.config.resource.GithubConfigurationResource;
+import org.sonar.server.v2.api.github.config.response.GithubConfigurationSearchRestResponse;
+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.GITHUB_CONFIGURATION_ENDPOINT;
+import static org.sonar.server.v2.WebApiEndpoints.INTERNAL;
+import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE;
+
+@RequestMapping(GITHUB_CONFIGURATION_ENDPOINT)
+@RestController
+public interface GithubConfigurationController {
+
+ @GetMapping(path = "/{id}")
+ @ResponseStatus(HttpStatus.OK)
+ @Operation(summary = "Fetch a GitHub configuration", description = """
+ Fetch a GitHub configuration. Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ GithubConfigurationResource getGithubConfiguration(
+ @PathVariable("id") @Parameter(description = "The id of the configuration to fetch.", required = true, in = ParameterIn.PATH) String id);
+
+ @GetMapping
+ @Operation(summary = "Search GitHub configs", description = """
+ Get the list of GitHub configurations.
+ Note that a single configuration is supported at this time.
+ Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ GithubConfigurationSearchRestResponse searchGithubConfiguration();
+
+ @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(HttpStatus.OK)
+ @Operation(summary = "Update a GitHub configuration", description = """
+ Update a GitHub configuration. Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ GithubConfigurationResource updateGithubConfiguration(@PathVariable("id") String id, @Valid @RequestBody GithubConfigurationUpdateRestRequest updateRequest);
+
+ @PostMapping
+ @Operation(summary = "Create GitHub configuration", description = """
+ Create a new GitHub configuration.
+ Note that only a single configuration can exist at a time.
+ Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ GithubConfigurationResource createGithubConfiguration(@Valid @RequestBody GithubConfigurationCreateRestRequest createRequest);
+
+ @DeleteMapping(path = "/{id}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @Operation(summary = "Delete a GitHub configuration", description = """
+ Delete a GitHub configuration.
+ Requires 'Administer System' permission.
+ """,
+ extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")}))
+ void deleteGithubConfiguration(
+ @PathVariable("id") @Parameter(description = "The id of the configuration to delete.", required = true, in = ParameterIn.PATH) String id);
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/package-info.java
new file mode 100644
index 00000000000..af6e4b356a1
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.config.controller;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/GithubConfigurationCreateRestRequest.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/GithubConfigurationCreateRestRequest.java
new file mode 100644
index 00000000000..d5cf09c7169
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/GithubConfigurationCreateRestRequest.java
@@ -0,0 +1,97 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.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.v2.api.model.ProvisioningType;
+
+public record GithubConfigurationCreateRestRequest(
+
+ @NotNull
+ @Schema(description = "Enable GitHub authentication")
+ boolean enabled,
+
+ @NotEmpty
+ @Schema(accessMode = Schema.AccessMode.WRITE_ONLY, description = "Client ID provided by GitHub when registering the application.")
+ String clientId,
+
+ @NotEmpty
+ @Schema(accessMode = Schema.AccessMode.WRITE_ONLY, description = "Client password provided by GitHub when registering the application.")
+ String clientSecret,
+
+ @NotEmpty
+ @Schema(description = "The App ID is found on your GitHub App's page on GitHub at Settings > Developer Settings > GitHub Apps.")
+ String applicationId,
+
+ @NotEmpty
+ @Schema(accessMode = Schema.AccessMode.WRITE_ONLY, description = """
+ Your GitHub App's private key. You can generate a .pem file from your GitHub App's page under Private keys.
+ Copy and paste the whole contents of the file here.
+ """)
+ String privateKey,
+
+ @NotNull
+ @Schema(description = """
+ Synchronize GitHub team with SonarQube group memberships when users log in to SonarQube.
+ For each GitHub team they belong to, users will be associated to a group of the same name if it exists in SonarQube.
+ """)
+ Boolean synchronizeGroups,
+
+ @NotEmpty
+ @Schema(description = "The API url for a GitHub instance. https://api.github.com/ for Github.com, https://github.company.com/api/v3/ when using Github Enterprise")
+ String apiUrl,
+
+ @NotEmpty
+ @Schema(description = "The WEB url for a GitHub instance. https://github.com/ for Github.com, https://github.company.com/ when using GitHub Enterprise.\n")
+ String webUrl,
+
+ @NotNull
+ @ArraySchema(arraySchema = @Schema(description = """
+ Only members of these organizations will be able to authenticate to the server.
+ ⚠ if not set, users from any organization where the GitHub App is installed will be able to login to this SonarQube instance.
+ """))
+ List<String> allowedOrganizations,
+
+ @NotNull
+ @Schema(description = "Type of synchronization")
+ ProvisioningType provisioningType,
+
+ @Nullable
+ @Schema(description = "Allow user to sign up")
+ Boolean allowUsersToSignUp,
+
+ @Nullable
+ @Schema(description = """
+ Change project visibility based on GitHub repository visibility.
+ If disabled, every provisioned project will be private in SonarQube and visible only to users with explicit GitHub permissions for the corresponding repository.
+ Changes take effect at the next synchronization.
+ """)
+ Boolean projectVisibility,
+
+ @Nullable
+ @Schema(description = "Admin consent to synchronize permissions from GitHub")
+ Boolean userConsentRequiredAfterUpgrade
+) {
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/GithubConfigurationUpdateRestRequest.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/GithubConfigurationUpdateRestRequest.java
new file mode 100644
index 00000000000..e25ebb1b22b
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/GithubConfigurationUpdateRestRequest.java
@@ -0,0 +1,160 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.config.request;
+
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.List;
+import org.sonar.server.v2.api.model.ProvisioningType;
+import org.sonar.server.v2.common.model.UpdateField;
+
+public class GithubConfigurationUpdateRestRequest {
+
+ private UpdateField<Boolean> enabled = UpdateField.undefined();
+ private UpdateField<String> clientId = UpdateField.undefined();
+ private UpdateField<String> clientSecret = UpdateField.undefined();
+ private UpdateField<String> applicationId = UpdateField.undefined();
+ private UpdateField<String> privateKey = UpdateField.undefined();
+ private UpdateField<Boolean> synchronizeGroups = UpdateField.undefined();
+ private UpdateField<String> apiUrl = UpdateField.undefined();
+ private UpdateField<String> webUrl = UpdateField.undefined();
+ private UpdateField<List<String>> allowedOrganizations = UpdateField.undefined();
+ private UpdateField<ProvisioningType> provisioningType = UpdateField.undefined();
+ private UpdateField<Boolean> allowUsersToSignUp = UpdateField.undefined();
+ private UpdateField<Boolean> projectVisibility = UpdateField.undefined();
+ private UpdateField<Boolean> userConsentRequiredAfterUpgrade = UpdateField.undefined();
+
+ @Schema(implementation = Boolean.class, description = "Enable GitHub authentication")
+ public UpdateField<Boolean> getEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(Boolean enabled) {
+ this.enabled = UpdateField.withValue(enabled);
+ }
+
+ @Schema(implementation = String.class, description = "GitHub Client ID")
+ public UpdateField<String> getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = UpdateField.withValue(clientId);
+ }
+
+ @Schema(implementation = String.class, description = "GitHub Client secret")
+ public UpdateField<String> getClientSecret() {
+ return clientSecret;
+ }
+
+ public void setClientSecret(String clientSecret) {
+ this.clientSecret = UpdateField.withValue(clientSecret);
+ }
+
+ @Schema(implementation = String.class, description = "GitHub Application id")
+ public UpdateField<String> getApplicationId() {
+ return applicationId;
+ }
+
+ public void setApplicationId(String applicationId) {
+ this.applicationId = UpdateField.withValue(applicationId);
+ }
+
+ @Schema(implementation = String.class, description = "GitHub Private key")
+ public UpdateField<String> getPrivateKey() {
+ return privateKey;
+ }
+
+ public void setPrivateKey(String privateKey) {
+ this.privateKey = UpdateField.withValue(privateKey);
+ }
+
+ @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 = String.class, description = "Url of GitHub instance for API connectivity (for instance https://api.github.com)")
+ public UpdateField<String> getApiUrl() {
+ return apiUrl;
+ }
+
+ public void setApiUrl(String apiUrl) {
+ this.apiUrl = UpdateField.withValue(apiUrl);
+ }
+
+ @Schema(implementation = String.class, description = "Url of GitHub instance for authentication (for instance https://github.com)")
+ public UpdateField<String> getWebUrl() {
+ return webUrl;
+ }
+
+ public void setWebUrl(String webUrl) {
+ this.webUrl = UpdateField.withValue(webUrl);
+ }
+
+ @ArraySchema(arraySchema = @Schema(description = "GitHub organizations allowed to authenticate and provisioned"), schema = @Schema(implementation = String.class))
+ public UpdateField<List<String>> getAllowedOrganizations() {
+ return allowedOrganizations;
+ }
+
+ public void setAllowedOrganizations(List<String> allowedOrganizations) {
+ this.allowedOrganizations = UpdateField.withValue(allowedOrganizations);
+ }
+
+ @Schema(implementation = ProvisioningType.class, description = "Type of synchronization")
+ public UpdateField<ProvisioningType> getProvisioningType() {
+ return provisioningType;
+ }
+
+ public void setProvisioningType(ProvisioningType provisioningType) {
+ this.provisioningType = UpdateField.withValue(provisioningType);
+ }
+
+ @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);
+ }
+
+ @Schema(implementation = Boolean.class, description = "Sync project visibility")
+ public UpdateField<Boolean> getProjectVisibility() {
+ return projectVisibility;
+ }
+
+ public void setProjectVisibility(Boolean projectVisibility) {
+ this.projectVisibility = UpdateField.withValue(projectVisibility);
+ }
+
+ @Schema(implementation = Boolean.class, description = "Admin consent to synchronize permissions from GitHub")
+ public UpdateField<Boolean> getUserConsentRequiredAfterUpgrade() {
+ return userConsentRequiredAfterUpgrade;
+ }
+
+ public void setUserConsentRequiredAfterUpgrade(Boolean userConsentRequiredAfterUpgrade) {
+ this.userConsentRequiredAfterUpgrade = UpdateField.withValue(userConsentRequiredAfterUpgrade);
+ }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/package-info.java
new file mode 100644
index 00000000000..9f9c01a6fe3
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/request/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.config.request;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/resource/GithubConfigurationResource.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/resource/GithubConfigurationResource.java
new file mode 100644
index 00000000000..fba9b7fa9a5
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/resource/GithubConfigurationResource.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.config.resource;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.sonar.server.v2.api.model.ProvisioningType;
+
+public record GithubConfigurationResource(
+
+ @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+ String id,
+
+ boolean enabled,
+
+ @Schema(implementation = String.class, description = "GitHub Application id")
+ String applicationId,
+
+ boolean synchronizeGroups,
+
+ @Schema(description = "Url of GitHub instance for API connectivity (for instance https://api.github.com)")
+ String apiUrl,
+
+ @Schema(description = "Url of GitHub instance for authentication (for instance https://github.com)")
+ String webUrl,
+
+ @Schema(description = "GitHub organizations allowed to authenticate and provisioned")
+ List<String> allowedOrganizations,
+
+ ProvisioningType provisioningType,
+
+ boolean allowUsersToSignUp,
+
+ boolean projectVisibility,
+
+ boolean userConsentRequiredAfterUpgrade,
+
+ @Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "In case the GitHub configuration is incorrect, error message")
+ @Nullable
+ String errorMessage
+) {
+}
+
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/resource/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/resource/package-info.java
new file mode 100644
index 00000000000..059cdfa6a12
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/resource/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.config.resource;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/response/GithubConfigurationSearchRestResponse.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/response/GithubConfigurationSearchRestResponse.java
new file mode 100644
index 00000000000..c9c5c189928
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/response/GithubConfigurationSearchRestResponse.java
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.config.response;
+
+import java.util.List;
+import org.sonar.server.v2.api.github.config.resource.GithubConfigurationResource;
+import org.sonar.server.v2.api.response.PageRestResponse;
+
+public record GithubConfigurationSearchRestResponse(List<GithubConfigurationResource> githubConfigurations, PageRestResponse page) {}
+
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/response/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/response/package-info.java
new file mode 100644
index 00000000000..5fa91f90eb2
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/response/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.config.response;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/DefaultGitlabConfigurationController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/DefaultGitlabConfigurationController.java
index d88fc9d8642..60bef449af6 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/DefaultGitlabConfigurationController.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/DefaultGitlabConfigurationController.java
@@ -136,11 +136,11 @@ public class DefaultGitlabConfigurationController implements GitlabConfiguration
configurationError.orElse(null));
}
- private static org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType toRestProvisioningType(GitlabConfiguration configuration) {
- return org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType.valueOf(configuration.provisioningType().name());
+ private static org.sonar.server.v2.api.model.ProvisioningType toRestProvisioningType(GitlabConfiguration configuration) {
+ return org.sonar.server.v2.api.model.ProvisioningType.valueOf(configuration.provisioningType().name());
}
- private static ProvisioningType toProvisioningType(org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType provisioningType) {
+ private static ProvisioningType toProvisioningType(org.sonar.server.v2.api.model.ProvisioningType provisioningType) {
return ProvisioningType.valueOf(provisioningType.name());
}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationCreateRestRequest.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationCreateRestRequest.java
index 5ba14e7db54..837328970ed 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationCreateRestRequest.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationCreateRestRequest.java
@@ -25,7 +25,7 @@ import java.util.List;
import javax.annotation.Nullable;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
-import org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType;
+import org.sonar.server.v2.api.model.ProvisioningType;
public record GitlabConfigurationCreateRestRequest(
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationUpdateRestRequest.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationUpdateRestRequest.java
index 6bc9f941b0d..57e38b911dd 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationUpdateRestRequest.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/request/GitlabConfigurationUpdateRestRequest.java
@@ -23,7 +23,7 @@ import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import javax.validation.constraints.Size;
-import org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType;
+import org.sonar.server.v2.api.model.ProvisioningType;
import org.sonar.server.v2.common.model.UpdateField;
public class GitlabConfigurationUpdateRestRequest {
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/GitlabConfigurationResource.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/GitlabConfigurationResource.java
index d8de23c8e05..075dddf02e8 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/GitlabConfigurationResource.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/GitlabConfigurationResource.java
@@ -22,6 +22,7 @@ package org.sonar.server.v2.api.gitlab.config.resource;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import javax.annotation.Nullable;
+import org.sonar.server.v2.api.model.ProvisioningType;
public record GitlabConfigurationResource(
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/ProvisioningType.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/model/ProvisioningType.java
index 3641019264c..46278857b1d 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/resource/ProvisioningType.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/model/ProvisioningType.java
@@ -17,7 +17,7 @@
* 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;
+package org.sonar.server.v2.api.model;
public enum ProvisioningType {
JIT, AUTO_PROVISIONING
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
index 7ec4a6387b8..7394832467b 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
@@ -24,6 +24,7 @@ import org.sonar.api.platform.Server;
import org.sonar.api.resources.Languages;
import org.sonar.db.Database;
import org.sonar.db.DbClient;
+import org.sonar.server.common.github.config.GithubConfigurationService;
import org.sonar.server.common.gitlab.config.GitlabConfigurationService;
import org.sonar.server.common.group.service.GroupMembershipService;
import org.sonar.server.common.group.service.GroupService;
@@ -48,10 +49,10 @@ import org.sonar.server.rule.RuleDescriptionFormatter;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;
import org.sonar.server.v2.api.analysis.controller.DefaultJresController;
+import org.sonar.server.v2.api.analysis.controller.DefaultScannerEngineController;
import org.sonar.server.v2.api.analysis.controller.DefaultVersionController;
import org.sonar.server.v2.api.analysis.controller.JresController;
import org.sonar.server.v2.api.analysis.controller.ScannerEngineController;
-import org.sonar.server.v2.api.analysis.controller.DefaultScannerEngineController;
import org.sonar.server.v2.api.analysis.controller.VersionController;
import org.sonar.server.v2.api.analysis.service.JresHandler;
import org.sonar.server.v2.api.analysis.service.JresHandlerImpl;
@@ -59,6 +60,8 @@ import org.sonar.server.v2.api.analysis.service.ScannerEngineHandler;
import org.sonar.server.v2.api.analysis.service.ScannerEngineHandlerImpl;
import org.sonar.server.v2.api.dop.controller.DefaultDopSettingsController;
import org.sonar.server.v2.api.dop.controller.DopSettingsController;
+import org.sonar.server.v2.api.github.config.controller.DefaultGithubConfigurationController;
+import org.sonar.server.v2.api.github.config.controller.GithubConfigurationController;
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;
@@ -159,6 +162,11 @@ public class PlatformLevel4WebConfig {
}
@Bean
+ public GithubConfigurationController githubConfigurationController(UserSession userSession, GithubConfigurationService githubConfigurationService) {
+ return new DefaultGithubConfigurationController(userSession, githubConfigurationService);
+ }
+
+ @Bean
public BoundProjectsController importedProjectsController(UserSession userSession, ImportProjectService importProjectService) {
return new DefaultBoundProjectsController(userSession, importProjectService);
}
diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/github/config/DefaultGithubConfigurationControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/github/config/DefaultGithubConfigurationControllerTest.java
new file mode 100644
index 00000000000..0153e73f38a
--- /dev/null
+++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/github/config/DefaultGithubConfigurationControllerTest.java
@@ -0,0 +1,499 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.github.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.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.server.common.NonNullUpdatedValue;
+import org.sonar.server.common.github.config.GithubConfiguration;
+import org.sonar.server.common.github.config.GithubConfigurationService;
+import org.sonar.server.common.github.config.UpdateGithubConfigurationRequest;
+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.github.config.controller.DefaultGithubConfigurationController;
+import org.sonar.server.v2.api.github.config.resource.GithubConfigurationResource;
+import org.sonar.server.v2.api.github.config.response.GithubConfigurationSearchRestResponse;
+import org.sonar.server.v2.api.model.ProvisioningType;
+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.ProvisioningType.AUTO_PROVISIONING;
+import static org.sonar.server.common.gitlab.config.ProvisioningType.JIT;
+import static org.sonar.server.v2.WebApiEndpoints.GITHUB_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 DefaultGithubConfigurationControllerTest {
+ private static final Gson GSON = new GsonBuilder().create();
+
+ private static final GithubConfiguration GITHUB_CONFIGURATION = new GithubConfiguration(
+ "existing-id",
+ true,
+ "client-id",
+ "client-secret",
+ "application-id",
+ "private-key",
+ true,
+ "api.url.com",
+ "www.url.com",
+ Set.of("org1", "org2"),
+ AUTO_PROVISIONING,
+ true,
+ true,
+ true
+ );
+
+ private static final GithubConfigurationResource EXPECTED_GITHUB_CONF_RESOURCE = new GithubConfigurationResource(
+ GITHUB_CONFIGURATION.id(),
+ GITHUB_CONFIGURATION.enabled(),
+ GITHUB_CONFIGURATION.applicationId(),
+ GITHUB_CONFIGURATION.synchronizeGroups(),
+ GITHUB_CONFIGURATION.apiUrl(),
+ GITHUB_CONFIGURATION.webUrl(),
+ List.of("org1", "org2"),
+ ProvisioningType.valueOf(GITHUB_CONFIGURATION.provisioningType().name()),
+ GITHUB_CONFIGURATION.allowUsersToSignUp(),
+ GITHUB_CONFIGURATION.provisionProjectVisibility(),
+ GITHUB_CONFIGURATION.userConsentRequiredAfterUpgrade(),
+ "error-message");
+
+ private static final String EXPECTED_CONFIGURATION = """
+ {
+ "id": "existing-id",
+ "enabled": true,
+ "applicationId": "application-id",
+ "synchronizeGroups": true,
+ "apiUrl": "api.url.com",
+ "webUrl": "www.url.com",
+ "allowedOrganizations": [
+ "org1",
+ "org2"
+ ],
+ "provisioningType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "projectVisibility": true,
+ "userConsentRequiredAfterUpgrade": true,
+ "errorMessage": "error-message"
+ }
+ """;
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+ private final GithubConfigurationService githubConfigurationService = mock();
+ private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultGithubConfigurationController(userSession, githubConfigurationService));
+
+ @Before
+ public void setUp() {
+ when(githubConfigurationService.validate(any())).thenReturn(Optional.of("error-message"));
+ }
+
+ @Test
+ public void fetchConfiguration_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
+ userSession.logIn().setNonSystemAdministrator();
+
+ mockMvc.perform(get(GITHUB_CONFIGURATION_ENDPOINT + "/1"))
+ .andExpectAll(
+ status().isForbidden(),
+ content().json("{\"message\":\"Insufficient privileges\"}"));
+ }
+
+ @Test
+ public void fetchConfiguration_whenConfigNotFound_throws() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(githubConfigurationService.getConfiguration("not-existing")).thenThrow(new NotFoundException("bla"));
+
+ mockMvc.perform(get(GITHUB_CONFIGURATION_ENDPOINT + "/not-existing"))
+ .andExpectAll(
+ status().isNotFound(),
+ content().json("{\"message\":\"bla\"}"));
+ }
+
+ @Test
+ public void fetchConfiguration_whenConfigFound_returnsIt() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(githubConfigurationService.getConfiguration("existing-id")).thenReturn(GITHUB_CONFIGURATION);
+
+ mockMvc.perform(get(GITHUB_CONFIGURATION_ENDPOINT + "/existing-id"))
+ .andExpectAll(
+ status().isOk(),
+ content().json(EXPECTED_CONFIGURATION));
+ }
+
+ @Test
+ public void search_whenNoParameters_shouldUseDefaultAndForwardToGroupMembershipService() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(githubConfigurationService.findConfigurations()).thenReturn(Optional.of(GITHUB_CONFIGURATION));
+
+ MvcResult mvcResult = mockMvc.perform(get(GITHUB_CONFIGURATION_ENDPOINT))
+ .andExpect(status().isOk())
+ .andReturn();
+
+ GithubConfigurationSearchRestResponse response = GSON.fromJson(mvcResult.getResponse().getContentAsString(), GithubConfigurationSearchRestResponse.class);
+
+ assertThat(response.page().pageSize()).isEqualTo(1000);
+ assertThat(response.page().pageIndex()).isEqualTo(1);
+ assertThat(response.page().total()).isEqualTo(1);
+ assertThat(response.githubConfigurations()).containsExactly(EXPECTED_GITHUB_CONF_RESOURCE);
+ }
+
+ @Test
+ public void search_whenNoParametersAndNoConfig_shouldReturnEmptyList() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(githubConfigurationService.findConfigurations()).thenReturn(Optional.empty());
+
+ MvcResult mvcResult = mockMvc.perform(get(GITHUB_CONFIGURATION_ENDPOINT))
+ .andExpect(status().isOk())
+ .andReturn();
+
+ GithubConfigurationSearchRestResponse response = GSON.fromJson(mvcResult.getResponse().getContentAsString(), GithubConfigurationSearchRestResponse.class);
+
+ assertThat(response.page().pageSize()).isEqualTo(1000);
+ assertThat(response.page().pageIndex()).isEqualTo(1);
+ assertThat(response.page().total()).isZero();
+ assertThat(response.githubConfigurations()).isEmpty();
+ }
+
+ @Test
+ public void updateConfiguration_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
+ userSession.logIn().setNonSystemAdministrator();
+
+ mockMvc.perform(patch(GITHUB_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(githubConfigurationService.updateConfiguration(any())).thenReturn(GITHUB_CONFIGURATION);
+
+ String payload = """
+ {
+ "enabled": true,
+ "clientId": "new-client-id",
+ "clientSecret": "new-client-secret",
+ "applicationId": "new-application-id",
+ "privateKey": "new-private-key",
+ "synchronizeGroups": false,
+ "apiUrl": "new-api.url.com",
+ "webUrl": "new-www.url.com",
+ "allowedOrganizations": [
+ "new-org1",
+ "new-org2"
+ ],
+ "provisioningType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": false,
+ "projectVisibility": false,
+ "userConsentRequiredAfterUpgrade": false
+ }
+ """;
+
+ mockMvc.perform(patch(GITHUB_CONFIGURATION_ENDPOINT + "/existing-id")
+ .contentType(JSON_MERGE_PATCH_CONTENT_TYPE)
+ .content(payload))
+ .andExpectAll(
+ status().isOk(),
+ content().json(EXPECTED_CONFIGURATION));
+
+ verify(githubConfigurationService).updateConfiguration(new UpdateGithubConfigurationRequest(
+ "existing-id",
+ NonNullUpdatedValue.withValueOrThrow(true),
+ NonNullUpdatedValue.withValueOrThrow("new-client-id"),
+ NonNullUpdatedValue.withValueOrThrow("new-client-secret"),
+ NonNullUpdatedValue.withValueOrThrow("new-application-id"),
+ NonNullUpdatedValue.withValueOrThrow("new-private-key"),
+ NonNullUpdatedValue.withValueOrThrow(false),
+ NonNullUpdatedValue.withValueOrThrow("new-api.url.com"),
+ NonNullUpdatedValue.withValueOrThrow("new-www.url.com"),
+ NonNullUpdatedValue.withValueOrThrow(Set.of("new-org1", "new-org2")),
+ NonNullUpdatedValue.withValueOrThrow(AUTO_PROVISIONING),
+ NonNullUpdatedValue.withValueOrThrow(false),
+ NonNullUpdatedValue.withValueOrThrow(false),
+ NonNullUpdatedValue.withValueOrThrow(false)
+ ));
+ }
+
+ @Test
+ public void updateConfiguration_whenSomeFieldsUpdated_performUpdates() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(githubConfigurationService.updateConfiguration(any())).thenReturn(GITHUB_CONFIGURATION);
+
+ String payload = """
+ {
+ "enabled": false,
+ "provisioningType": "JIT",
+ "allowUsersToSignUp": false
+ }
+ """;
+
+ mockMvc.perform(patch(GITHUB_CONFIGURATION_ENDPOINT + "/existing-id")
+ .contentType(JSON_MERGE_PATCH_CONTENT_TYPE)
+ .content(payload))
+ .andExpectAll(
+ status().isOk(),
+ content().json(EXPECTED_CONFIGURATION));
+
+ verify(githubConfigurationService).updateConfiguration(new UpdateGithubConfigurationRequest(
+ "existing-id",
+ NonNullUpdatedValue.withValueOrThrow(false),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.withValueOrThrow(JIT),
+ NonNullUpdatedValue.withValueOrThrow(false),
+ NonNullUpdatedValue.undefined(),
+ NonNullUpdatedValue.undefined()
+ ));
+ }
+
+ @Test
+ public void create_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
+ userSession.logIn().setNonSystemAdministrator();
+
+ mockMvc.perform(
+ post(GITHUB_CONFIGURATION_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("""
+ {
+ "enabled": true,
+ "clientId": "new-client-id",
+ "clientSecret": "new-client-secret",
+ "applicationId": "new-application-id",
+ "privateKey": "new-private-key",
+ "synchronizeGroups": false,
+ "apiUrl": "new-api.url.com",
+ "webUrl": "new-www.url.com",
+ "allowedOrganizations": [
+ "new-org1",
+ "new-org2"
+ ],
+ "provisioningType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": false,
+ "projectVisibility": false,
+ "userConsentRequiredAfterUpgrade": false
+ }
+ """))
+ .andExpectAll(
+ status().isForbidden(),
+ content().json("{\"message\":\"Insufficient privileges\"}"));
+ }
+
+ @Test
+ public void create_whenConfigCreated_returnsIt() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(githubConfigurationService.createConfiguration(any())).thenReturn(GITHUB_CONFIGURATION);
+
+ mockMvc.perform(
+ post(GITHUB_CONFIGURATION_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("""
+ {
+ "enabled": true,
+ "clientId": "client-id",
+ "clientSecret": "client-secret",
+ "applicationId": "application-id",
+ "privateKey": "private-key",
+ "synchronizeGroups": true,
+ "apiUrl": "api.url.com",
+ "webUrl": "www.url.com",
+ "allowedOrganizations": [
+ "org1",
+ "org2"
+ ],
+ "provisioningType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "projectVisibility": true,
+ "userConsentRequiredAfterUpgrade": true
+ }
+ """))
+ .andExpectAll(
+ status().isOk(),
+ content().json("""
+ {
+ "enabled": true,
+ "applicationId": "application-id",
+ "synchronizeGroups": true,
+ "apiUrl": "api.url.com",
+ "webUrl": "www.url.com",
+ "allowedOrganizations": [
+ "org1",
+ "org2"
+ ],
+ "provisioningType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "projectVisibility": true,
+ "userConsentRequiredAfterUpgrade": true
+ }
+ """));
+
+ }
+ @Test
+ public void create_whenConfigCreatedWithoutOptionalParams_returnsIt() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ when(githubConfigurationService.createConfiguration(any())).thenReturn(new GithubConfiguration(
+ "existing-id",
+ true,
+ "client-id",
+ "client-secret",
+ "application-id",
+ "private-key",
+ true,
+ "api.url.com",
+ "www.url.com",
+ Set.of(),
+ AUTO_PROVISIONING,
+ false,
+ false,
+ false
+ ));
+
+ mockMvc.perform(
+ post(GITHUB_CONFIGURATION_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("""
+ {
+ "enabled": true,
+ "clientId": "client-id",
+ "clientSecret": "client-secret",
+ "applicationId": "application-id",
+ "privateKey": "private-key",
+ "synchronizeGroups": true,
+ "apiUrl": "api.url.com",
+ "webUrl": "www.url.com",
+ "allowedOrganizations": [],
+ "provisioningType": "AUTO_PROVISIONING"
+ }
+ """))
+ .andExpectAll(
+ status().isOk(),
+ content().json("""
+ {
+ "id": "existing-id",
+ "enabled": true,
+ "applicationId": "application-id",
+ "synchronizeGroups": true,
+ "apiUrl": "api.url.com",
+ "webUrl": "www.url.com",
+ "allowedOrganizations": [],
+ "provisioningType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": false,
+ "projectVisibility": false,
+ "userConsentRequiredAfterUpgrade": false
+ }
+ """));
+
+ }
+
+ @Test
+ public void create_whenRequiredParameterIsMissing_shouldReturnBadRequest() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+
+ mockMvc.perform(
+ post(GITHUB_CONFIGURATION_ENDPOINT)
+ .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .content("""
+ {
+ "enabled": true,
+ "clientId": "client-id",
+ "clientSecret": "client-secret",
+ "privateKey": "private-key",
+ "synchronizeGroups": true,
+ "apiUrl": "api.url.com",
+ "webUrl": "www.url.com",
+ "allowedOrganizations": [
+ "org1",
+ "org2"
+ ],
+ "provisioningType": "AUTO_PROVISIONING",
+ "allowUsersToSignUp": true,
+ "projectVisibility": true,
+ "userConsentRequiredAfterUpgrade": true
+ }
+ """))
+ .andExpectAll(
+ status().isBadRequest(),
+ content().json(
+ "{\"message\":\"Value {} for field applicationId was rejected. Error: must not be empty.\"}"));
+
+ }
+
+ @Test
+ public void delete_whenUserIsNotAdministrator_shouldReturnForbidden() throws Exception {
+ userSession.logIn().setNonSystemAdministrator();
+
+ mockMvc.perform(
+ delete(GITHUB_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(GITHUB_CONFIGURATION_ENDPOINT + "/existing-id"))
+ .andExpectAll(
+ status().isNoContent());
+
+ verify(githubConfigurationService).deleteConfiguration("existing-id");
+ }
+
+ @Test
+ public void delete_whenConfigNotFound_returnsNotFound() throws Exception {
+ userSession.logIn().setSystemAdministrator();
+ doThrow(new NotFoundException("Not found")).when(githubConfigurationService).deleteConfiguration("not-existing");
+
+ mockMvc.perform(
+ delete(GITHUB_CONFIGURATION_ENDPOINT + "/not-existing"))
+ .andExpectAll(
+ status().isNotFound(),
+ content().json("{\"message\":\"Not found\"}"));
+ }
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/gitlab/config/DefaultGitlabConfigurationControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/gitlab/config/DefaultGitlabConfigurationControllerTest.java
index 26e1ae364f6..ea85b7e8b9f 100644
--- a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/gitlab/config/DefaultGitlabConfigurationControllerTest.java
+++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/gitlab/config/DefaultGitlabConfigurationControllerTest.java
@@ -38,6 +38,7 @@ 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.sonar.server.v2.api.model.ProvisioningType;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -83,7 +84,7 @@ public class DefaultGitlabConfigurationControllerTest {
GITLAB_CONFIGURATION.synchronizeGroups(),
List.of("group1", "group2"),
GITLAB_CONFIGURATION.allowUsersToSignUp(),
- org.sonar.server.v2.api.gitlab.config.resource.ProvisioningType.valueOf(GITLAB_CONFIGURATION.provisioningType().name()),
+ ProvisioningType.valueOf(GITLAB_CONFIGURATION.provisioningType().name()),
!GITLAB_CONFIGURATION.provisioningToken().isEmpty(),
"error-message");