diff options
author | Antoine Vigneau <antoine.vigneau@sonarsource.com> | 2024-06-11 17:44:45 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-06-17 20:02:35 +0000 |
commit | 078306d53ad53ba38d5d4b06e6e8958a0c2c6595 (patch) | |
tree | cd0ac74f560aac0e2a3720b096239d7519f856c0 /server/sonar-webserver-webapi-v2/src | |
parent | 0bdfddeed0bf06255f61c6b59dcfc6d132598e14 (diff) | |
download | sonarqube-078306d53ad53ba38d5d4b06e6e8958a0c2c6595.tar.gz sonarqube-078306d53ad53ba38d5d4b06e6e8958a0c2c6595.zip |
SONAR-22365 Fix SSF-571
Diffstat (limited to 'server/sonar-webserver-webapi-v2/src')
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"); |