From e9fa96f54d2e21bafde5dcd9fb9d388df914cd08 Mon Sep 17 00:00:00 2001 From: Wojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:54:49 +0100 Subject: [PATCH] SONAR-21088 Fix SSF-434 --- .../alm/client/ApplicationHttpClient.java | 2 +- .../client/GenericApplicationHttpClient.java | 2 +- .../client/GenericPaginatedHttpClient.java | 2 +- .../sonar/alm/client/PaginatedHttpClient.java | 2 +- .../github/GithubApplicationClientImpl.java | 37 +++-- .../github/GithubGlobalSettingsValidator.java | 3 +- .../GithubProvisioningConfigValidator.java | 8 +- .../alm/client/github/security/AppToken.java | 1 + .../sonar/alm/client/gitlab/GitlabToken.java | 2 +- .../GenericPaginatedHttpClientImplTest.java | 2 +- .../GenericApplicationHttpClientTest.java | 4 +- .../GithubApplicationClientImplTest.java | 15 +- .../GithubGlobalSettingsValidatorTest.java | 2 +- ...GithubProvisioningConfigValidatorTest.java | 8 +- .../security/GithubAppSecurityImplTest.java | 2 +- server/sonar-auth-github/build.gradle | 1 + .../auth}/github/AppInstallationToken.java | 4 +- .../auth/github/GitHubIdentityProvider.java | 58 +++++-- .../org/sonar/auth/github/GitHubModule.java | 5 +- .../sonar/auth/github/GitHubRestClient.java | 8 +- .../org/sonar/auth/github/GitHubSettings.java | 8 +- .../auth/github}/GithubAppConfiguration.java | 2 +- .../auth/github}/GithubAppInstallation.java | 4 +- .../org/sonar/auth}/github/GithubBinding.java | 24 ++- .../github}/GsonRepositoryCollaborator.java | 2 +- .../auth/github}/GsonRepositoryTeam.java | 2 +- .../client}/GithubApplicationClient.java | 20 +-- .../auth/github/client}/package-info.java | 2 +- .../github/scribe/ScribeServiceBuilder.java | 35 ++++ .../auth/github/scribe/package-info.java | 23 +++ .../auth}/github/security/AccessToken.java | 2 +- .../github/security/UserAccessToken.java | 2 +- .../auth/github/security/package-info.java | 23 +++ .../github/AppInstallationTokenTest.java | 2 +- .../github/GitHubIdentityProviderTest.java | 152 +++++++++++++++--- .../sonar/auth/github/GitHubModuleTest.java | 2 +- .../github}/GithubAppConfigurationTest.java | 2 +- .../sonar/auth/github/IntegrationTest.java | 51 +++++- .../github/ImportGithubProjectActionIT.java | 6 +- .../ListGithubOrganizationsActionIT.java | 4 +- .../ListGithubRepositoriesActionIT.java | 2 +- .../server/ce/queue/ReportSubmitterIT.java | 2 +- .../ws/github/ImportGithubProjectAction.java | 4 +- .../github/ListGithubOrganizationsAction.java | 8 +- .../github/ListGithubRepositoriesAction.java | 8 +- .../ws/GithubProjectCreationParameters.java | 4 +- .../almsettings/ws/GithubProjectCreator.java | 10 +- .../ws/GithubProjectCreatorFactory.java | 8 +- .../ws/GithubProjectCreatorFactoryTest.java | 4 +- .../ws/GithubProjectCreatorTest.java | 10 +- 50 files changed, 449 insertions(+), 147 deletions(-) rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client => sonar-auth-github/src/main/java/org/sonar/auth}/github/AppInstallationToken.java (96%) rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client/github/config => sonar-auth-github/src/main/java/org/sonar/auth/github}/GithubAppConfiguration.java (98%) rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client/github/config => sonar-auth-github/src/main/java/org/sonar/auth/github}/GithubAppInstallation.java (91%) rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client => sonar-auth-github/src/main/java/org/sonar/auth}/github/GithubBinding.java (96%) rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client/github/api => sonar-auth-github/src/main/java/org/sonar/auth/github}/GsonRepositoryCollaborator.java (96%) rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client/github/api => sonar-auth-github/src/main/java/org/sonar/auth/github}/GsonRepositoryTeam.java (96%) rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client/github => sonar-auth-github/src/main/java/org/sonar/auth/github/client}/GithubApplicationClient.java (94%) rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client/github/api => sonar-auth-github/src/main/java/org/sonar/auth/github/client}/package-info.java (95%) create mode 100644 server/sonar-auth-github/src/main/java/org/sonar/auth/github/scribe/ScribeServiceBuilder.java create mode 100644 server/sonar-auth-github/src/main/java/org/sonar/auth/github/scribe/package-info.java rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client => sonar-auth-github/src/main/java/org/sonar/auth}/github/security/AccessToken.java (95%) rename server/{sonar-alm-client/src/main/java/org/sonar/alm/client => sonar-auth-github/src/main/java/org/sonar/auth}/github/security/UserAccessToken.java (96%) create mode 100644 server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/package-info.java rename server/{sonar-alm-client/src/test/java/org/sonar/alm/client => sonar-auth-github/src/test/java/org/sonar/auth}/github/AppInstallationTokenTest.java (97%) rename server/{sonar-alm-client/src/test/java/org/sonar/alm/client/github/config => sonar-auth-github/src/test/java/org/sonar/auth/github}/GithubAppConfigurationTest.java (99%) diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/ApplicationHttpClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/ApplicationHttpClient.java index 3f94e2d6e24..5d9d91c1413 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/ApplicationHttpClient.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/ApplicationHttpClient.java @@ -21,7 +21,7 @@ package org.sonar.alm.client; import java.io.IOException; import java.util.Optional; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.security.AccessToken; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.server.ServerSide; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericApplicationHttpClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericApplicationHttpClient.java index 5a86476a074..5d600a06ccb 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericApplicationHttpClient.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericApplicationHttpClient.java @@ -37,7 +37,7 @@ import okhttp3.ResponseBody; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.security.AccessToken; import org.sonarqube.ws.client.OkHttpClientBuilder; import static com.google.common.base.Preconditions.checkArgument; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericPaginatedHttpClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericPaginatedHttpClient.java index 577c09adb78..74835e08794 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericPaginatedHttpClient.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericPaginatedHttpClient.java @@ -26,7 +26,7 @@ import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.alm.client.ApplicationHttpClient.GetResponse; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.security.AccessToken; import static java.lang.String.format; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/PaginatedHttpClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/PaginatedHttpClient.java index 94a134da12e..29aae912797 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/PaginatedHttpClient.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/PaginatedHttpClient.java @@ -21,7 +21,7 @@ package org.sonar.alm.client; import java.util.List; import java.util.function.Function; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.security.AccessToken; public interface PaginatedHttpClient { List get(String appUrl, AccessToken token, String query, Function> responseDeserializer); diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java index ef230b7b90f..8e855dbd19a 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java @@ -39,20 +39,23 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.alm.client.ApplicationHttpClient; import org.sonar.alm.client.ApplicationHttpClient.GetResponse; -import org.sonar.alm.client.github.GithubBinding.GsonGithubRepository; -import org.sonar.alm.client.github.GithubBinding.GsonInstallations; -import org.sonar.alm.client.github.GithubBinding.GsonRepositorySearch; -import org.sonar.alm.client.github.api.GsonRepositoryCollaborator; -import org.sonar.alm.client.github.api.GsonRepositoryTeam; -import org.sonar.alm.client.github.config.GithubAppConfiguration; -import org.sonar.alm.client.github.config.GithubAppInstallation; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.GithubBinding; +import org.sonar.auth.github.GithubBinding.GsonGithubRepository; +import org.sonar.auth.github.GithubBinding.GsonInstallations; +import org.sonar.auth.github.GithubBinding.GsonRepositorySearch; +import org.sonar.auth.github.GsonRepositoryCollaborator; +import org.sonar.auth.github.GsonRepositoryTeam; +import org.sonar.auth.github.GithubAppConfiguration; +import org.sonar.auth.github.GithubAppInstallation; +import org.sonar.auth.github.security.AccessToken; import org.sonar.alm.client.github.security.AppToken; import org.sonar.alm.client.github.security.GithubAppSecurity; -import org.sonar.alm.client.github.security.UserAccessToken; +import org.sonar.auth.github.security.UserAccessToken; import org.sonar.alm.client.gitlab.GsonApp; import org.sonar.api.internal.apachecommons.lang.StringUtils; import org.sonar.auth.github.GitHubSettings; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonar.server.exceptions.ServerException; import org.sonarqube.ws.client.HttpException; @@ -193,11 +196,11 @@ public class GithubApplicationClientImpl implements GithubApplicationClient { return organizations; } - organizations.setTotal(gsonInstallations.get().totalCount); - if (gsonInstallations.get().installations != null) { - organizations.setOrganizations(gsonInstallations.get().installations.stream() - .map(gsonInstallation -> new Organization(gsonInstallation.account.id, gsonInstallation.account.login, null, null, null, null, null, - gsonInstallation.targetType)) + organizations.setTotal(gsonInstallations.get().getTotalCount()); + if (gsonInstallations.get().getInstallations() != null) { + organizations.setOrganizations(gsonInstallations.get().getInstallations().stream() + .map(gsonInstallation -> new Organization(gsonInstallation.getAccount().getId(), gsonInstallation.getAccount().getLogin(), null, null, null, null, null, + gsonInstallation.getTargetType())) .toList()); } @@ -267,10 +270,10 @@ public class GithubApplicationClientImpl implements GithubApplicationClient { return repositories; } - repositories.setTotal(gsonRepositories.get().totalCount); + repositories.setTotal(gsonRepositories.get().getTotalCount()); - if (gsonRepositories.get().items != null) { - repositories.setRepositories(gsonRepositories.get().items.stream() + if (gsonRepositories.get().getItems() != null) { + repositories.setRepositories(gsonRepositories.get().getItems().stream() .map(GsonGithubRepository::toRepository) .toList()); } diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubGlobalSettingsValidator.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubGlobalSettingsValidator.java index 3a462d9cbd0..49753899765 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubGlobalSettingsValidator.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubGlobalSettingsValidator.java @@ -20,10 +20,11 @@ package org.sonar.alm.client.github; import java.util.Optional; -import org.sonar.alm.client.github.config.GithubAppConfiguration; +import org.sonar.auth.github.GithubAppConfiguration; import org.sonar.api.config.internal.Encryption; import org.sonar.api.config.internal.Settings; import org.sonar.api.server.ServerSide; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonar.db.alm.setting.AlmSettingDto; import static org.apache.commons.lang.StringUtils.isBlank; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubProvisioningConfigValidator.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubProvisioningConfigValidator.java index 227b35f7774..a7c19cab96a 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubProvisioningConfigValidator.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubProvisioningConfigValidator.java @@ -22,15 +22,17 @@ package org.sonar.alm.client.github.config; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import org.sonar.alm.client.github.GithubApplicationClient; -import org.sonar.alm.client.github.GithubBinding.Permissions; +import org.sonar.auth.github.GithubAppConfiguration; +import org.sonar.auth.github.GithubAppInstallation; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.GithubBinding.Permissions; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.server.ServerSide; import org.sonar.auth.github.GitHubSettings; import org.sonarqube.ws.client.HttpException; import static java.lang.Long.parseLong; -import static org.sonar.alm.client.github.GithubBinding.GsonApp; +import static org.sonar.auth.github.GithubBinding.GsonApp; import static org.sonar.alm.client.github.config.ConfigCheckResult.ApplicationStatus; import static org.sonar.alm.client.github.config.ConfigCheckResult.ConfigStatus; import static org.sonar.alm.client.github.config.ConfigCheckResult.InstallationStatus; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/AppToken.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/AppToken.java index 0cc8f81032a..0ef5e1d7b1c 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/AppToken.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/AppToken.java @@ -20,6 +20,7 @@ package org.sonar.alm.client.github.security; import javax.annotation.concurrent.Immutable; +import org.sonar.auth.github.security.AccessToken; import static java.util.Objects.requireNonNull; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabToken.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabToken.java index 54a122e809c..d7ea1a6a880 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabToken.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabToken.java @@ -20,7 +20,7 @@ package org.sonar.alm.client.gitlab; import java.util.Objects; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.security.AccessToken; public class GitlabToken implements AccessToken { private final String token; diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java index b2eb590c6a5..1d3785333ef 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java @@ -33,7 +33,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.slf4j.event.Level; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.security.AccessToken; import org.sonar.api.testfixtures.log.LogTester; import static org.assertj.core.api.Assertions.assertThat; diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GenericApplicationHttpClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GenericApplicationHttpClientTest.java index e23c0b0fe72..89fea3115ce 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GenericApplicationHttpClientTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GenericApplicationHttpClientTest.java @@ -41,8 +41,8 @@ import org.sonar.alm.client.GenericApplicationHttpClient; import org.sonar.alm.client.TimeoutConfiguration; import org.sonar.alm.client.ApplicationHttpClient.GetResponse; import org.sonar.alm.client.ApplicationHttpClient.Response; -import org.sonar.alm.client.github.security.AccessToken; -import org.sonar.alm.client.github.security.UserAccessToken; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.auth.github.security.UserAccessToken; import org.sonar.api.testfixtures.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java index 158dc31ceab..346090977b9 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java @@ -37,19 +37,22 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.slf4j.event.Level; import org.sonar.alm.client.ApplicationHttpClient.RateLimit; -import org.sonar.alm.client.github.api.GsonRepositoryCollaborator; -import org.sonar.alm.client.github.api.GsonRepositoryTeam; -import org.sonar.alm.client.github.config.GithubAppConfiguration; -import org.sonar.alm.client.github.config.GithubAppInstallation; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.GsonRepositoryCollaborator; +import org.sonar.auth.github.GsonRepositoryTeam; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.GithubAppConfiguration; +import org.sonar.auth.github.GithubAppInstallation; +import org.sonar.auth.github.GithubBinding; +import org.sonar.auth.github.security.AccessToken; import org.sonar.alm.client.github.security.AppToken; import org.sonar.alm.client.github.security.GithubAppSecurity; -import org.sonar.alm.client.github.security.UserAccessToken; +import org.sonar.auth.github.security.UserAccessToken; import org.sonar.api.testfixtures.log.LogAndArguments; import org.sonar.api.testfixtures.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.auth.github.GitHubSettings; import org.sonar.auth.github.GsonRepositoryPermissions; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonarqube.ws.client.HttpException; import static java.net.HttpURLConnection.HTTP_CREATED; diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubGlobalSettingsValidatorTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubGlobalSettingsValidatorTest.java index c9c80ab464f..b21520c8ac2 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubGlobalSettingsValidatorTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubGlobalSettingsValidatorTest.java @@ -23,7 +23,7 @@ import javax.annotation.Nullable; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.sonar.alm.client.github.config.GithubAppConfiguration; +import org.sonar.auth.github.GithubAppConfiguration; import org.sonar.api.config.internal.Encryption; import org.sonar.api.config.internal.Settings; import org.sonar.db.alm.setting.ALM; diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/config/GithubProvisioningConfigValidatorTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/config/GithubProvisioningConfigValidatorTest.java index 1882ad7e6d4..966fe6db701 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/config/GithubProvisioningConfigValidatorTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/config/GithubProvisioningConfigValidatorTest.java @@ -27,9 +27,11 @@ import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.sonar.alm.client.github.GithubApplicationClient; -import org.sonar.alm.client.github.GithubBinding.GsonApp; -import org.sonar.alm.client.github.GithubBinding.Permissions; +import org.sonar.auth.github.GithubAppConfiguration; +import org.sonar.auth.github.GithubAppInstallation; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.GithubBinding.GsonApp; +import org.sonar.auth.github.GithubBinding.Permissions; import org.sonar.auth.github.GitHubSettings; import org.sonarqube.ws.client.HttpException; diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/security/GithubAppSecurityImplTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/security/GithubAppSecurityImplTest.java index c6e6e8b69f5..b80445eed32 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/security/GithubAppSecurityImplTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/security/GithubAppSecurityImplTest.java @@ -28,7 +28,7 @@ import java.time.ZoneId; import java.util.Random; import org.junit.Test; import org.junit.runner.RunWith; -import org.sonar.alm.client.github.config.GithubAppConfiguration; +import org.sonar.auth.github.GithubAppConfiguration; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; diff --git a/server/sonar-auth-github/build.gradle b/server/sonar-auth-github/build.gradle index 62325070c81..0a016cca648 100644 --- a/server/sonar-auth-github/build.gradle +++ b/server/sonar-auth-github/build.gradle @@ -20,6 +20,7 @@ dependencies { testImplementation 'com.squareup.okhttp3:mockwebserver' testImplementation 'com.squareup.okhttp3:okhttp' + testImplementation 'com.tngtech.java:junit-dataprovider' testImplementation 'junit:junit' testImplementation 'org.assertj:assertj-core' testImplementation 'org.mockito:mockito-core' diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/AppInstallationToken.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/AppInstallationToken.java similarity index 96% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/AppInstallationToken.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/AppInstallationToken.java index cf9a1b8584a..04a9b817695 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/AppInstallationToken.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/AppInstallationToken.java @@ -17,10 +17,10 @@ * 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.alm.client.github; +package org.sonar.auth.github; import javax.annotation.concurrent.Immutable; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.security.AccessToken; import static java.util.Objects.requireNonNull; diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java index 37135fe984f..5d8fbca23e6 100644 --- a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubIdentityProvider.java @@ -23,15 +23,21 @@ import com.github.scribejava.core.builder.ServiceBuilder; import com.github.scribejava.core.model.OAuth2AccessToken; import com.github.scribejava.core.oauth.OAuth20Service; import java.io.IOException; +import java.util.List; +import java.util.Optional; import java.util.concurrent.ExecutionException; import org.sonar.api.server.authentication.Display; import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.UnauthorizedException; import org.sonar.api.server.authentication.UserIdentity; import org.sonar.api.server.http.HttpRequest; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.scribe.ScribeServiceBuilder; import static com.google.common.base.Preconditions.checkState; +import static java.lang.Long.parseLong; import static java.lang.String.format; +import static org.sonar.auth.github.GitHubSettings.DEFAULT_API_URL; public class GitHubIdentityProvider implements OAuth2IdentityProvider { @@ -41,12 +47,17 @@ public class GitHubIdentityProvider implements OAuth2IdentityProvider { private final UserIdentityFactory userIdentityFactory; private final ScribeGitHubApi scribeApi; private final GitHubRestClient gitHubRestClient; + private final GithubApplicationClient githubAppClient; + private final ScribeServiceBuilder scribeServiceBuilder; - public GitHubIdentityProvider(GitHubSettings settings, UserIdentityFactory userIdentityFactory, ScribeGitHubApi scribeApi, GitHubRestClient gitHubRestClient) { + public GitHubIdentityProvider(GitHubSettings settings, UserIdentityFactory userIdentityFactory, ScribeGitHubApi scribeApi, GitHubRestClient gitHubRestClient, + GithubApplicationClient githubAppClient, ScribeServiceBuilder scribeServiceBuilder) { this.settings = settings; this.userIdentityFactory = userIdentityFactory; this.scribeApi = scribeApi; this.gitHubRestClient = gitHubRestClient; + this.githubAppClient = githubAppClient; + this.scribeServiceBuilder = scribeServiceBuilder; } @Override @@ -107,7 +118,8 @@ public class GitHubIdentityProvider implements OAuth2IdentityProvider { context.verifyCsrfState(); HttpRequest request = context.getHttpRequest(); - OAuth20Service scribe = newScribeBuilder(context).build(scribeApi); + OAuth20Service scribe = scribeServiceBuilder.buildScribeService(settings.clientId(), settings.clientSecret(), context.getCallbackUrl(), scribeApi); + String code = request.getParameter("code"); OAuth2AccessToken accessToken = scribe.getAccessToken(code); @@ -128,18 +140,25 @@ public class GitHubIdentityProvider implements OAuth2IdentityProvider { context.redirectToRequestedPage(); } - boolean isOrganizationMembershipRequired() { - return !settings.getOrganizations().isEmpty(); + private void check(OAuth20Service scribe, OAuth2AccessToken accessToken, GsonUser user) throws InterruptedException, ExecutionException, IOException { + if (!isUserAuthorized(scribe, accessToken, user.getLogin())) { + String message = settings.getOrganizations().isEmpty() + ? format("'%s' must be a member of at least one organization which has installed the SonarQube GitHub app", user.getLogin()) + : format("'%s' must be a member of at least one organization: '%s'", user.getLogin(), String.join("', '", settings.getOrganizations().stream().sorted().toList())); + throw new UnauthorizedException(message); + } } - private void check(OAuth20Service scribe, OAuth2AccessToken accessToken, GsonUser user) throws InterruptedException, ExecutionException, IOException { - if (isUnauthorized(scribe, accessToken, user.getLogin())) { - throw new UnauthorizedException(format("'%s' must be a member of at least one organization: '%s'", user.getLogin(), String.join("', '", settings.getOrganizations()))); + private boolean isUserAuthorized(OAuth20Service scribe, OAuth2AccessToken accessToken, String login) throws IOException, ExecutionException, InterruptedException { + if (isOrganizationMembershipRequired()) { + return isOrganizationsMember(scribe, accessToken, login); + } else { + return isMemberOfInstallationOrganization(scribe, accessToken, login); } } - private boolean isUnauthorized(OAuth20Service scribe, OAuth2AccessToken accessToken, String login) throws IOException, ExecutionException, InterruptedException { - return isOrganizationMembershipRequired() && !isOrganizationsMember(scribe, accessToken, login); + private boolean isOrganizationMembershipRequired() { + return !settings.getOrganizations().isEmpty(); } private boolean isOrganizationsMember(OAuth20Service scribe, OAuth2AccessToken accessToken, String login) throws IOException, ExecutionException, InterruptedException { @@ -151,6 +170,27 @@ public class GitHubIdentityProvider implements OAuth2IdentityProvider { return false; } + private boolean isMemberOfInstallationOrganization(OAuth20Service scribe, OAuth2AccessToken accessToken, String login) + throws IOException, ExecutionException, InterruptedException { + GithubAppConfiguration githubAppConfiguration = githubAppConfiguration(); + List githubAppInstallations = githubAppClient.getWhitelistedGithubAppInstallations(githubAppConfiguration); + for (GithubAppInstallation installation : githubAppInstallations) { + if (gitHubRestClient.isOrganizationMember(scribe, accessToken, installation.organizationName(), login)) { + return true; + } + } + return false; + } + + private GithubAppConfiguration githubAppConfiguration() { + String apiEndpoint = Optional.ofNullable(settings.apiURL()).orElse(DEFAULT_API_URL); + try { + return new GithubAppConfiguration(parseLong(settings.appId()), settings.privateKey(), apiEndpoint); + } catch (NumberFormatException numberFormatException) { + throw new IllegalStateException("Github configuration is not complete. Please check your configuration under the Authentication > GitHub tab"); + } + } + private ServiceBuilder newScribeBuilder(OAuth2IdentityProvider.OAuth2Context context) { checkState(isEnabled(), "GitHub authentication is disabled"); return new ServiceBuilder(settings.clientId()) diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubModule.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubModule.java index 993aebb91b1..8559069b994 100644 --- a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubModule.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubModule.java @@ -21,6 +21,7 @@ package org.sonar.auth.github; import java.util.List; import org.sonar.api.config.PropertyDefinition; +import org.sonar.auth.github.scribe.ScribeServiceBuilder; import org.sonar.core.platform.Module; import static org.sonar.auth.github.GitHubSettings.definitions; @@ -30,8 +31,8 @@ public class GitHubModule extends Module { GitHubIdentityProvider.class, GitHubRestClient.class, UserIdentityFactoryImpl.class, - ScribeGitHubApi.class - ); + ScribeGitHubApi.class, + ScribeServiceBuilder.class); @Override protected void configureModule() { diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubRestClient.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubRestClient.java index 4e6a2e4f679..cda94d0cd17 100644 --- a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubRestClient.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubRestClient.java @@ -45,13 +45,13 @@ public class GitHubRestClient { this.settings = settings; } - GsonUser getUser(OAuth20Service scribe, OAuth2AccessToken accessToken) throws IOException { + public GsonUser getUser(OAuth20Service scribe, OAuth2AccessToken accessToken) throws IOException { String responseBody = executeRequest(settings.apiURL() + "user", scribe, accessToken).getBody(); LOGGER.trace("User response received : {}", responseBody); return GsonUser.parse(responseBody); } - String getEmail(OAuth20Service scribe, OAuth2AccessToken accessToken) throws IOException { + public String getEmail(OAuth20Service scribe, OAuth2AccessToken accessToken) throws IOException { String responseBody = executeRequest(settings.apiURL() + "user/emails", scribe, accessToken).getBody(); LOGGER.trace("Emails response received : {}", responseBody); List emails = GsonEmail.parse(responseBody); @@ -62,7 +62,7 @@ public class GitHubRestClient { .orElse(null); } - List getTeams(OAuth20Service scribe, OAuth2AccessToken accessToken) { + public List getTeams(OAuth20Service scribe, OAuth2AccessToken accessToken) { return executePaginatedRequest(settings.apiURL() + "user/teams", scribe, accessToken, GsonTeam::parse); } @@ -74,7 +74,7 @@ public class GitHubRestClient { * * @see GitHub members API */ - boolean isOrganizationMember(OAuth20Service scribe, OAuth2AccessToken accessToken, String organization, String login) + public boolean isOrganizationMember(OAuth20Service scribe, OAuth2AccessToken accessToken, String organization, String login) throws IOException, ExecutionException, InterruptedException { String requestUrl = settings.apiURL() + format("orgs/%s/members/%s", organization, login); OAuthRequest request = new OAuthRequest(Verb.GET, requestUrl); diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java index 9e79ea7b3af..d1d9e32f03a 100644 --- a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java @@ -81,11 +81,11 @@ public class GitHubSettings implements DevOpsPlatformSettings { this.dbClient = dbClient; } - String clientId() { + public String clientId() { return configuration.get(CLIENT_ID).orElse(""); } - String clientSecret() { + public String clientSecret() { return configuration.get(CLIENT_SECRET).orElse(""); } @@ -101,11 +101,11 @@ public class GitHubSettings implements DevOpsPlatformSettings { return configuration.getBoolean(ENABLED).orElse(false) && !clientId().isEmpty() && !clientSecret().isEmpty(); } - boolean allowUsersToSignUp() { + public boolean allowUsersToSignUp() { return configuration.getBoolean(ALLOW_USERS_TO_SIGN_UP).orElse(false); } - boolean syncGroups() { + public boolean syncGroups() { return configuration.getBoolean(GROUPS_SYNC).orElse(false); } diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubAppConfiguration.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubAppConfiguration.java similarity index 98% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubAppConfiguration.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubAppConfiguration.java index f73daee7672..34643c471a3 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubAppConfiguration.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubAppConfiguration.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.alm.client.github.config; +package org.sonar.auth.github; import com.google.common.base.MoreObjects; import java.util.regex.Pattern; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubAppInstallation.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubAppInstallation.java similarity index 91% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubAppInstallation.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubAppInstallation.java index b58a024b63f..fe8b0c9308d 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/config/GithubAppInstallation.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubAppInstallation.java @@ -17,8 +17,6 @@ * 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.alm.client.github.config; - -import org.sonar.alm.client.github.GithubBinding; +package org.sonar.auth.github; public record GithubAppInstallation(String installationId, String organizationName, GithubBinding.Permissions permissions, boolean isSuspended) {} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubBinding.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubBinding.java similarity index 96% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubBinding.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubBinding.java index 709f4bf45fd..65759e010a2 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubBinding.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GithubBinding.java @@ -17,14 +17,14 @@ * 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.alm.client.github; +package org.sonar.auth.github; import com.google.gson.annotations.SerializedName; import java.util.List; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import static org.sonar.alm.client.github.GithubApplicationClient.Repository; +import static org.sonar.auth.github.client.GithubApplicationClient.Repository; public class GithubBinding { @@ -71,6 +71,14 @@ public class GithubBinding { // recommended: // http://stackoverflow.com/a/18645370/229031 } + + public int getTotalCount() { + return totalCount; + } + + public List getInstallations() { + return installations; + } } public static class GsonInstallation { @@ -133,6 +141,10 @@ public class GithubBinding { // http://stackoverflow.com/a/18645370/229031 } + public long getId() { + return id; + } + public String getLogin() { return login; } @@ -282,6 +294,14 @@ public class GithubBinding { // recommended: // http://stackoverflow.com/a/18645370/229031 } + public int getTotalCount() { + return totalCount; + } + + public List getItems() { + return items; + } + } public static class GsonGithubRepository { diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryCollaborator.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GsonRepositoryCollaborator.java similarity index 96% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryCollaborator.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/GsonRepositoryCollaborator.java index dd92d26d8a0..98cef5abc94 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryCollaborator.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GsonRepositoryCollaborator.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.alm.client.github.api; +package org.sonar.auth.github; import com.google.gson.annotations.SerializedName; import org.sonar.auth.github.GsonRepositoryPermissions; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryTeam.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GsonRepositoryTeam.java similarity index 96% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryTeam.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/GsonRepositoryTeam.java index c8e2dcd2ea0..f694ce0b154 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryTeam.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/GsonRepositoryTeam.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.alm.client.github.api; +package org.sonar.auth.github; import com.google.gson.annotations.SerializedName; import org.sonar.auth.github.GsonRepositoryPermissions; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/client/GithubApplicationClient.java similarity index 94% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/client/GithubApplicationClient.java index ccea31bb78f..ef8a6285dfd 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/client/GithubApplicationClient.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.alm.client.github; +package org.sonar.auth.github.client; import com.google.gson.annotations.SerializedName; import java.util.List; @@ -26,13 +26,15 @@ import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import org.sonar.alm.client.github.api.GsonRepositoryCollaborator; -import org.sonar.alm.client.github.api.GsonRepositoryTeam; -import org.sonar.alm.client.github.config.GithubAppConfiguration; -import org.sonar.alm.client.github.config.GithubAppInstallation; -import org.sonar.alm.client.github.security.AccessToken; -import org.sonar.alm.client.github.security.UserAccessToken; import org.sonar.api.server.ServerSide; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.GithubAppConfiguration; +import org.sonar.auth.github.GithubAppInstallation; +import org.sonar.auth.github.GithubBinding; +import org.sonar.auth.github.GsonRepositoryCollaborator; +import org.sonar.auth.github.GsonRepositoryTeam; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.auth.github.security.UserAccessToken; @ServerSide public interface GithubApplicationClient { @@ -105,7 +107,7 @@ public interface GithubApplicationClient { private List repositories; public Repositories() { - //nothing to do + // nothing to do } public int getTotal() { @@ -204,7 +206,7 @@ public interface GithubApplicationClient { private List organizations; public Organizations() { - //nothing to do + // nothing to do } public int getTotal() { diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/package-info.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/client/package-info.java similarity index 95% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/package-info.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/client/package-info.java index 24a912c1e6e..6b632a6238d 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/package-info.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/client/package-info.java @@ -18,6 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @ParametersAreNonnullByDefault -package org.sonar.alm.client.github.api; +package org.sonar.auth.github.client; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/scribe/ScribeServiceBuilder.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/scribe/ScribeServiceBuilder.java new file mode 100644 index 00000000000..cffcce9ff1b --- /dev/null +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/scribe/ScribeServiceBuilder.java @@ -0,0 +1,35 @@ +/* + * 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.auth.github.scribe; + +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.oauth.OAuth20Service; +import org.sonar.auth.github.ScribeGitHubApi; + +public class ScribeServiceBuilder { + + + public OAuth20Service buildScribeService(String clientId, String clientSecret, String callbackUrl, ScribeGitHubApi scribeApi) { + return new ServiceBuilder(clientId) + .apiSecret(clientSecret) + .callback(callbackUrl) + .build(scribeApi); + } +} diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/scribe/package-info.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/scribe/package-info.java new file mode 100644 index 00000000000..800b91f2bb8 --- /dev/null +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/scribe/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.auth.github.scribe; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/AccessToken.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/AccessToken.java similarity index 95% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/AccessToken.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/AccessToken.java index 1f4b232bc7c..3812c760ff3 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/AccessToken.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/AccessToken.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.alm.client.github.security; +package org.sonar.auth.github.security; /** * Token used to authenticate requests to Github API diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/UserAccessToken.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/UserAccessToken.java similarity index 96% rename from server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/UserAccessToken.java rename to server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/UserAccessToken.java index a74db7fc9a0..ea41eeb13f0 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/security/UserAccessToken.java +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/UserAccessToken.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.alm.client.github.security; +package org.sonar.auth.github.security; public class UserAccessToken implements AccessToken { diff --git a/server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/package-info.java b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/package-info.java new file mode 100644 index 00000000000..d04ef7553ab --- /dev/null +++ b/server/sonar-auth-github/src/main/java/org/sonar/auth/github/security/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.auth.github.security; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/AppInstallationTokenTest.java b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/AppInstallationTokenTest.java similarity index 97% rename from server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/AppInstallationTokenTest.java rename to server/sonar-auth-github/src/test/java/org/sonar/auth/github/AppInstallationTokenTest.java index 8aae06669ca..dd943ed5998 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/AppInstallationTokenTest.java +++ b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/AppInstallationTokenTest.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.alm.client.github; +package org.sonar.auth.github; import org.junit.Test; diff --git a/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubIdentityProviderTest.java b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubIdentityProviderTest.java index 910f59161fe..7e7aab267f2 100644 --- a/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubIdentityProviderTest.java +++ b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubIdentityProviderTest.java @@ -19,28 +19,49 @@ */ package org.sonar.auth.github; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.oauth.OAuth20Service; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutionException; import org.junit.Test; import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; +import org.sonar.api.server.authentication.UnauthorizedException; +import org.sonar.api.server.authentication.UserIdentity; +import org.sonar.api.server.http.HttpRequest; +import org.sonar.auth.github.GithubBinding.Permissions; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.scribe.ScribeServiceBuilder; import org.sonar.db.DbClient; import org.sonar.server.property.InternalProperties; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sonar.api.server.authentication.OAuth2IdentityProvider.CallbackContext; +import static org.sonar.api.server.authentication.OAuth2IdentityProvider.InitContext; +import static org.sonar.auth.github.GitHubSettings.APP_ID; +import static org.sonar.auth.github.GitHubSettings.CLIENT_ID; +import static org.sonar.auth.github.GitHubSettings.CLIENT_SECRET; +import static org.sonar.auth.github.GitHubSettings.ENABLED; +import static org.sonar.auth.github.GitHubSettings.ORGANIZATIONS; +import static org.sonar.auth.github.GitHubSettings.PRIVATE_KEY; public class GitHubIdentityProviderTest { - private MapSettings settings = new MapSettings(); private InternalProperties internalProperties = mock(InternalProperties.class); private GitHubSettings gitHubSettings = new GitHubSettings(settings.asConfig(), internalProperties, mock(DbClient.class)); private UserIdentityFactoryImpl userIdentityFactory = mock(UserIdentityFactoryImpl.class); private ScribeGitHubApi scribeApi = new ScribeGitHubApi(gitHubSettings); - private GitHubRestClient gitHubRestClient = new GitHubRestClient(gitHubSettings); - private GitHubIdentityProvider underTest = new GitHubIdentityProvider(gitHubSettings, userIdentityFactory, scribeApi, gitHubRestClient); + private GitHubRestClient gitHubRestClient = mock(); + private GithubApplicationClient githubAppClient = mock(); + + private ScribeServiceBuilder scribeServiceBuilder = mock(); + private GitHubIdentityProvider underTest = new GitHubIdentityProvider(gitHubSettings, userIdentityFactory, scribeApi, gitHubRestClient, githubAppClient, scribeServiceBuilder); @Test public void check_fields() { @@ -72,7 +93,7 @@ public class GitHubIdentityProviderTest { @Test public void init() { setSettings(true); - OAuth2IdentityProvider.InitContext context = mock(OAuth2IdentityProvider.InitContext.class); + InitContext context = mock(InitContext.class); when(context.generateCsrfState()).thenReturn("state"); when(context.getCallbackUrl()).thenReturn("http://localhost/callback"); settings.setProperty("sonar.auth.github.webUrl", "https://github.com/"); @@ -91,7 +112,7 @@ public class GitHubIdentityProviderTest { setSettings(true); settings.setProperty("sonar.auth.github.groupsSync", "true"); settings.setProperty("sonar.auth.github.webUrl", "https://github.com/"); - OAuth2IdentityProvider.InitContext context = mock(OAuth2IdentityProvider.InitContext.class); + InitContext context = mock(InitContext.class); when(context.generateCsrfState()).thenReturn("state"); when(context.getCallbackUrl()).thenReturn("http://localhost/callback"); @@ -109,7 +130,7 @@ public class GitHubIdentityProviderTest { setSettings(true); settings.setProperty("sonar.auth.github.organizations", "example"); settings.setProperty("sonar.auth.github.webUrl", "https://github.com/"); - OAuth2IdentityProvider.InitContext context = mock(OAuth2IdentityProvider.InitContext.class); + InitContext context = mock(InitContext.class); when(context.generateCsrfState()).thenReturn("state"); when(context.getCallbackUrl()).thenReturn("http://localhost/callback"); @@ -126,7 +147,7 @@ public class GitHubIdentityProviderTest { @Test public void fail_to_init_when_disabled() { setSettings(false); - OAuth2IdentityProvider.InitContext context = mock(OAuth2IdentityProvider.InitContext.class); + InitContext context = mock(InitContext.class); assertThatThrownBy(() -> underTest.init(context)) .isInstanceOf(IllegalStateException.class) @@ -155,28 +176,117 @@ public class GitHubIdentityProviderTest { } @Test - public void organization_membership_required() { - setSettings(true); - settings.setProperty("sonar.auth.github.organizations", "example"); - assertThat(underTest.isOrganizationMembershipRequired()).isTrue(); - settings.setProperty("sonar.auth.github.organizations", "example0, example1"); - assertThat(underTest.isOrganizationMembershipRequired()).isTrue(); + public void callback_whenOrganizationsAreDefinedAndUserBelongsToOne_shouldAuthenticateAndRedirect() throws IOException, ExecutionException, InterruptedException { + UserIdentity userIdentity = mock(UserIdentity.class); + CallbackContext context = mockUserBelongingToOrganization(userIdentity); + + settings.setProperty(ORGANIZATIONS, "organization1,organization2"); + underTest.callback(context); + + verify(context).authenticate(userIdentity); + verify(context).redirectToRequestedPage(); } @Test - public void organization_membership_not_required() { + public void callback_whenOrganizationsAreDefinedAndDoesntBelongToOne_shouldThrow() throws IOException, ExecutionException, InterruptedException { + UserIdentity userIdentity = mock(UserIdentity.class); + CallbackContext context = mockUserNotBelongingToOrganization(userIdentity); + + settings.setProperty(ORGANIZATIONS, "organization1,organization2"); + + assertThatThrownBy(() -> underTest.callback(context)) + .isInstanceOf(UnauthorizedException.class) + .hasMessage("'login' must be a member of at least one organization: 'organization1', 'organization2'"); + } + + @Test + public void callback_whenOrganizationsAreNotDefinedAndUserBelongsToInstallationOrganization_shouldAuthenticateAndRedirect() + throws IOException, ExecutionException, InterruptedException { + UserIdentity userIdentity = mock(UserIdentity.class); + CallbackContext context = mockUserBelongingToOrganization(userIdentity); + + mockInstallations(); + + underTest.callback(context); + + verify(context).authenticate(userIdentity); + verify(context).redirectToRequestedPage(); + } + + @Test + public void callback_whenOrganizationsAreNotDefinedAndUserDoesntBelongToInstallationOrganization_shouldThrow() throws IOException, ExecutionException, InterruptedException { + UserIdentity userIdentity = mock(UserIdentity.class); + CallbackContext context = mockUserNotBelongingToOrganization(userIdentity); + + mockInstallations(); + + assertThatThrownBy(() -> underTest.callback(context)) + .isInstanceOf(UnauthorizedException.class) + .hasMessage("'login' must be a member of at least one organization which has installed the SonarQube GitHub app"); + } + + private CallbackContext mockUserBelongingToOrganization(UserIdentity userIdentity) throws IOException, InterruptedException, ExecutionException { setSettings(true); - settings.setProperty("sonar.auth.github.organizations", ""); - assertThat(underTest.isOrganizationMembershipRequired()).isFalse(); + CallbackContext context = mock(); + HttpRequest httpRequest = mock(); + OAuth20Service scribeService = mock(); + GsonUser user = new GsonUser("id", "login", "name", "email"); + + OAuth2AccessToken accessToken = mockAccessToken(scribeService, context, httpRequest, user); + + when(gitHubRestClient.isOrganizationMember(scribeService, accessToken, "organization1", "login")).thenReturn(false); + when(gitHubRestClient.isOrganizationMember(scribeService, accessToken, "organization2", "login")).thenReturn(true); + + when(userIdentityFactory.create(user, "email", null)).thenReturn(userIdentity); + return context; + } + + private CallbackContext mockUserNotBelongingToOrganization(UserIdentity userIdentity) throws IOException, InterruptedException, ExecutionException { + setSettings(true); + CallbackContext context = mock(); + HttpRequest httpRequest = mock(); + OAuth20Service scribeService = mock(); + GsonUser user = new GsonUser("id", "login", "name", "email"); + + OAuth2AccessToken accessToken = mockAccessToken(scribeService, context, httpRequest, user); + + when(gitHubRestClient.isOrganizationMember(scribeService, accessToken, "organization1", "login")).thenReturn(false); + when(gitHubRestClient.isOrganizationMember(scribeService, accessToken, "organization2", "login")).thenReturn(false); + + when(userIdentityFactory.create(user, "email", null)).thenReturn(userIdentity); + return context; + } + + private OAuth2AccessToken mockAccessToken(OAuth20Service scribeService, CallbackContext context, HttpRequest httpRequest, GsonUser user) + throws IOException, InterruptedException, ExecutionException { + String callbackUrl = "http://localhost/callback"; + when(scribeServiceBuilder.buildScribeService("id", "secret", callbackUrl, scribeApi)).thenReturn(scribeService); + when(context.getHttpRequest()).thenReturn(httpRequest); + when(context.getCallbackUrl()).thenReturn(callbackUrl); + + when(httpRequest.getParameter("code")).thenReturn("code"); + OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class); + when(scribeService.getAccessToken("code")).thenReturn(accessToken); + + when(gitHubRestClient.getUser(scribeService, accessToken)).thenReturn(user); + return accessToken; + } + + private void mockInstallations() { + when(githubAppClient.getWhitelistedGithubAppInstallations(any())).thenReturn(List.of( + new GithubAppInstallation("1", "organization1", new Permissions(), false), + new GithubAppInstallation("2", "organization2", new Permissions(), false))); } private void setSettings(boolean enabled) { if (enabled) { - settings.setProperty("sonar.auth.github.clientId.secured", "id"); - settings.setProperty("sonar.auth.github.clientSecret.secured", "secret"); - settings.setProperty("sonar.auth.github.enabled", true); + settings.setProperty(CLIENT_ID, "id"); + settings.setProperty(CLIENT_SECRET, "secret"); + settings.setProperty(ENABLED, true); + settings.setProperty(APP_ID, "1"); + settings.setProperty(PRIVATE_KEY, "private"); } else { - settings.setProperty("sonar.auth.github.enabled", false); + settings.setProperty(ENABLED, false); } } } diff --git a/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubModuleTest.java b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubModuleTest.java index fc24c654281..ef4fc73eada 100644 --- a/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubModuleTest.java +++ b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GitHubModuleTest.java @@ -30,7 +30,7 @@ public class GitHubModuleTest { public void verify_count_of_added_components() { ListContainer container = new ListContainer(); new GitHubModule().configure(container); - assertThat(container.getAddedObjects()).hasSize(15); + assertThat(container.getAddedObjects()).hasSize(16); } } diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/config/GithubAppConfigurationTest.java b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GithubAppConfigurationTest.java similarity index 99% rename from server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/config/GithubAppConfigurationTest.java rename to server/sonar-auth-github/src/test/java/org/sonar/auth/github/GithubAppConfigurationTest.java index 698faab2a69..4426514d2f3 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/config/GithubAppConfigurationTest.java +++ b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/GithubAppConfigurationTest.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.alm.client.github.config; +package org.sonar.auth.github; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; diff --git a/server/sonar-auth-github/src/test/java/org/sonar/auth/github/IntegrationTest.java b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/IntegrationTest.java index a6170b77cde..3e4a29a9c48 100644 --- a/server/sonar-auth-github/src/test/java/org/sonar/auth/github/IntegrationTest.java +++ b/server/sonar-auth-github/src/test/java/org/sonar/auth/github/IntegrationTest.java @@ -22,6 +22,7 @@ package org.sonar.auth.github; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean; import javax.servlet.http.HttpServletRequest; @@ -40,6 +41,8 @@ import org.sonar.api.server.authentication.UserIdentity; import org.sonar.api.server.http.HttpRequest; import org.sonar.api.server.http.HttpResponse; import org.sonar.api.utils.System2; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.scribe.ScribeServiceBuilder; import org.sonar.db.DbTester; import org.sonar.server.http.JavaxHttpRequest; import org.sonar.server.property.InternalProperties; @@ -47,7 +50,9 @@ import org.sonar.server.property.InternalPropertiesImpl; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -69,9 +74,13 @@ public class IntegrationTest { private ScribeGitHubApi scribeApi = new ScribeGitHubApi(gitHubSettings); private GitHubRestClient gitHubRestClient = new GitHubRestClient(gitHubSettings); + private GithubApplicationClient githubAppClient = mock(); + + private ScribeServiceBuilder scribeServiceBuilder = new ScribeServiceBuilder(); + private String gitHubUrl; - private GitHubIdentityProvider underTest = new GitHubIdentityProvider(gitHubSettings, userIdentityFactory, scribeApi, gitHubRestClient); + private GitHubIdentityProvider underTest = new GitHubIdentityProvider(gitHubSettings, userIdentityFactory, scribeApi, gitHubRestClient, githubAppClient, scribeServiceBuilder); @Before public void enable() { @@ -81,6 +90,8 @@ public class IntegrationTest { settings.setProperty("sonar.auth.github.enabled", true); settings.setProperty("sonar.auth.github.apiUrl", gitHubUrl); settings.setProperty("sonar.auth.github.webUrl", gitHubUrl); + settings.setProperty("sonar.auth.github.appId", "1"); + settings.setProperty("sonar.auth.github.privateKey.secured", "private_key"); } /** @@ -115,9 +126,15 @@ public class IntegrationTest { github.enqueue(newSuccessfulAccessTokenResponse()); // response of api.github.com/user github.enqueue(new MockResponse().setBody("{\"id\":\"ABCD\", \"login\":\"octocat\", \"name\":\"monalisa octocat\",\"email\":\"octocat@github.com\"}")); + // response of api.github.com/orgs/first_org/members/user + github.enqueue(new MockResponse().setResponseCode(404)); + // response of api.github.com/orgs/second_org/members/user + github.enqueue(new MockResponse().setResponseCode(204)); HttpServletRequest request = newRequest("the-verifier-code"); DumbCallbackContext callbackContext = new DumbCallbackContext(request); + mockInstallations(); + underTest.callback(callbackContext); assertThat(callbackContext.csrfStateVerified.get()).isTrue(); @@ -146,6 +163,8 @@ public class IntegrationTest { github.enqueue(newSuccessfulAccessTokenResponse()); // response of api.github.com/user github.enqueue(new MockResponse().setBody("{\"id\":\"ABCD\", \"login\":\"octocat\", \"name\":\"monalisa octocat\",\"email\":null}")); + // response of api.github.com/orgs/first_org/members/user + github.enqueue(new MockResponse().setResponseCode(204)); // response of api.github.com/user/emails github.enqueue(new MockResponse().setBody( "[\n" + @@ -163,6 +182,8 @@ public class IntegrationTest { HttpServletRequest request = newRequest("the-verifier-code"); DumbCallbackContext callbackContext = new DumbCallbackContext(request); + mockInstallations(); + underTest.callback(callbackContext); assertThat(callbackContext.csrfStateVerified.get()).isTrue(); @@ -178,11 +199,16 @@ public class IntegrationTest { github.enqueue(newSuccessfulAccessTokenResponse()); // response of api.github.com/user github.enqueue(new MockResponse().setBody("{\"id\":\"ABCD\", \"login\":\"octocat\", \"name\":\"monalisa octocat\",\"email\":null}")); + // response of api.github.com/orgs/first_org/members/user + github.enqueue(new MockResponse().setResponseCode(204)); // response of api.github.com/user/emails github.enqueue(new MockResponse().setBody("[]")); + HttpServletRequest request = newRequest("the-verifier-code"); DumbCallbackContext callbackContext = new DumbCallbackContext(request); + mockInstallations(); + underTest.callback(callbackContext); assertThat(callbackContext.csrfStateVerified.get()).isTrue(); @@ -215,6 +241,8 @@ public class IntegrationTest { github.enqueue(newSuccessfulAccessTokenResponse()); // response of api.github.com/user github.enqueue(new MockResponse().setBody("{\"id\":\"ABCD\", \"login\":\"octocat\", \"name\":\"monalisa octocat\",\"email\":\"octocat@github.com\"}")); + // response of api.github.com/orgs/first_org/members/user + github.enqueue(new MockResponse().setResponseCode(204)); // response of api.github.com/user/teams github.enqueue(new MockResponse().setBody("[\n" + " {\n" + @@ -227,6 +255,8 @@ public class IntegrationTest { HttpServletRequest request = newRequest("the-verifier-code"); DumbCallbackContext callbackContext = new DumbCallbackContext(request); + mockInstallations(); + underTest.callback(callbackContext); assertThat(callbackContext.userIdentity.getGroups()).containsOnly("SonarSource/developers"); @@ -239,6 +269,8 @@ public class IntegrationTest { github.enqueue(newSuccessfulAccessTokenResponse()); // response of api.github.com/user github.enqueue(new MockResponse().setBody("{\"id\":\"ABCD\", \"login\":\"octocat\", \"name\":\"monalisa octocat\",\"email\":\"octocat@github.com\"}")); + // response of api.github.com/orgs/first_org/members/user + github.enqueue(new MockResponse().setResponseCode(204)); // responses of api.github.com/user/teams github.enqueue(new MockResponse() .setHeader("Link", "<" + gitHubUrl + "/user/teams?per_page=100&page=2>; rel=\"next\", <" + gitHubUrl + "/user/teams?per_page=100&page=2>; rel=\"last\"") @@ -263,6 +295,8 @@ public class IntegrationTest { HttpServletRequest request = newRequest("the-verifier-code"); DumbCallbackContext callbackContext = new DumbCallbackContext(request); + mockInstallations(); + underTest.callback(callbackContext); assertThat(new TreeSet<>(callbackContext.userIdentity.getGroups())).containsOnly("SonarQubeCommunity/sonarsource-developers", "SonarSource/developers"); @@ -316,12 +350,9 @@ public class IntegrationTest { HttpServletRequest request = newRequest("the-verifier-code"); DumbCallbackContext callbackContext = new DumbCallbackContext(request); - try { - underTest.callback(callbackContext); - fail("exception expected"); - } catch (UnauthorizedException e) { - assertThat(e.getMessage()).contains("'octocat' must be a member of at least one organization:", "'first_org'", "'second_org'"); - } + assertThatThrownBy(() -> underTest.callback(callbackContext)) + .isInstanceOf(UnauthorizedException.class) + .hasMessage("'octocat' must be a member of at least one organization: 'first_org', 'second_org'"); } @Test @@ -378,6 +409,12 @@ public class IntegrationTest { return request; } + private void mockInstallations() { + when(githubAppClient.getWhitelistedGithubAppInstallations(any())).thenReturn(List.of( + new GithubAppInstallation("1", "first_org", new GithubBinding.Permissions(), false), + new GithubAppInstallation("2", "second_org", new GithubBinding.Permissions(), false))); + } + private static class DumbCallbackContext implements OAuth2IdentityProvider.CallbackContext { final HttpServletRequest request; final AtomicBoolean csrfStateVerified = new AtomicBoolean(false); diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java index 79b27f5eeb0..bf458239a21 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectActionIT.java @@ -25,10 +25,10 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.sonar.alm.client.github.AppInstallationToken; -import org.sonar.alm.client.github.GithubApplicationClient; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonar.alm.client.github.GithubApplicationClientImpl; -import org.sonar.alm.client.github.api.GsonRepositoryCollaborator; +import org.sonar.auth.github.GsonRepositoryCollaborator; import org.sonar.api.resources.Qualifiers; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ListGithubOrganizationsActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ListGithubOrganizationsActionIT.java index 458ea1dbc54..079078606d4 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ListGithubOrganizationsActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ListGithubOrganizationsActionIT.java @@ -24,9 +24,9 @@ import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; -import org.sonar.alm.client.github.GithubApplicationClient; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonar.alm.client.github.GithubApplicationClientImpl; -import org.sonar.alm.client.github.security.UserAccessToken; +import org.sonar.auth.github.security.UserAccessToken; import org.sonar.api.config.internal.Encryption; import org.sonar.api.config.internal.Settings; import org.sonar.api.server.ws.WebService; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ListGithubRepositoriesActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ListGithubRepositoriesActionIT.java index 93e42a7e503..53007fe1a4e 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ListGithubRepositoriesActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/almintegration/ws/github/ListGithubRepositoriesActionIT.java @@ -22,7 +22,7 @@ package org.sonar.server.almintegration.ws.github; import java.util.stream.Stream; import org.junit.Rule; import org.junit.Test; -import org.sonar.alm.client.github.GithubApplicationClient; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonar.alm.client.github.GithubApplicationClientImpl; import org.sonar.api.utils.System2; import org.sonar.db.DbTester; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java index ae602a575d8..c539bced594 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ce/queue/ReportSubmitterIT.java @@ -27,7 +27,7 @@ import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.sonar.alm.client.github.GithubApplicationClient; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonar.alm.client.github.GithubGlobalSettingsValidator; import org.sonar.api.utils.System2; import org.sonar.auth.github.GitHubSettings; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java index 16efbf36762..f936cb944ca 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ImportGithubProjectAction.java @@ -21,8 +21,8 @@ package org.sonar.server.almintegration.ws.github; import java.util.Optional; import javax.inject.Inject; -import org.sonar.alm.client.github.security.AccessToken; -import org.sonar.alm.client.github.security.UserAccessToken; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.auth.github.security.UserAccessToken; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ListGithubOrganizationsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ListGithubOrganizationsAction.java index 5db9343acac..203f52c959d 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ListGithubOrganizationsAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ListGithubOrganizationsAction.java @@ -21,11 +21,11 @@ package org.sonar.server.almintegration.ws.github; import java.util.List; import java.util.Optional; -import org.sonar.alm.client.github.GithubApplicationClient; -import org.sonar.alm.client.github.GithubApplicationClient.Organization; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.client.GithubApplicationClient.Organization; import org.sonar.alm.client.github.GithubApplicationClientImpl; -import org.sonar.alm.client.github.security.AccessToken; -import org.sonar.alm.client.github.security.UserAccessToken; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.auth.github.security.UserAccessToken; import org.sonar.api.config.internal.Encryption; import org.sonar.api.config.internal.Settings; import org.sonar.api.server.ws.Request; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ListGithubRepositoriesAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ListGithubRepositoriesAction.java index 8525d40a1c9..352aeaeca17 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ListGithubRepositoriesAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/github/ListGithubRepositoriesAction.java @@ -27,11 +27,11 @@ import java.util.Set; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.stream.Collectors; -import org.sonar.alm.client.github.GithubApplicationClient; -import org.sonar.alm.client.github.GithubApplicationClient.Repository; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.client.GithubApplicationClient.Repository; import org.sonar.alm.client.github.GithubApplicationClientImpl; -import org.sonar.alm.client.github.security.AccessToken; -import org.sonar.alm.client.github.security.UserAccessToken; +import org.sonar.auth.github.security.AccessToken; +import org.sonar.auth.github.security.UserAccessToken; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreationParameters.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreationParameters.java index 475440fefb3..dd4d19100bf 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreationParameters.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreationParameters.java @@ -20,8 +20,8 @@ package org.sonar.server.almsettings.ws; import javax.annotation.Nullable; -import org.sonar.alm.client.github.AppInstallationToken; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.security.AccessToken; import org.sonar.db.alm.setting.AlmSettingDto; import org.sonar.server.user.UserSession; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreator.java index a7a769ea5fb..81767282b9c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreator.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreator.java @@ -23,12 +23,12 @@ import java.util.Optional; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.sonar.alm.client.github.AppInstallationToken; -import org.sonar.alm.client.github.GithubApplicationClient; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonar.alm.client.github.GithubPermissionConverter; -import org.sonar.alm.client.github.api.GsonRepositoryCollaborator; -import org.sonar.alm.client.github.api.GsonRepositoryTeam; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.GsonRepositoryCollaborator; +import org.sonar.auth.github.GsonRepositoryTeam; +import org.sonar.auth.github.security.AccessToken; import org.sonar.api.web.UserRole; import org.sonar.auth.github.GsonRepositoryPermissions; import org.sonar.db.DbClient; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java index c9df2edd617..cf63d1045e4 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactory.java @@ -23,12 +23,12 @@ import java.util.Map; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.alm.client.github.AppInstallationToken; -import org.sonar.alm.client.github.GithubApplicationClient; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonar.alm.client.github.GithubGlobalSettingsValidator; import org.sonar.alm.client.github.GithubPermissionConverter; -import org.sonar.alm.client.github.config.GithubAppConfiguration; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.GithubAppConfiguration; +import org.sonar.auth.github.security.AccessToken; import org.sonar.api.server.ServerSide; import org.sonar.auth.github.GitHubSettings; import org.sonar.db.DbClient; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java index 4c0c3039a58..c90c25d4a11 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorFactoryTest.java @@ -28,8 +28,8 @@ import org.mockito.Answers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.sonar.alm.client.github.AppInstallationToken; -import org.sonar.alm.client.github.GithubApplicationClient; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.client.GithubApplicationClient; import org.sonar.alm.client.github.GithubGlobalSettingsValidator; import org.sonar.alm.client.github.GithubPermissionConverter; import org.sonar.auth.github.GitHubSettings; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorTest.java index 6cdb34631d8..7156bba2d17 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almsettings/ws/GithubProjectCreatorTest.java @@ -31,11 +31,11 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.sonar.alm.client.github.AppInstallationToken; -import org.sonar.alm.client.github.GithubApplicationClient; -import org.sonar.alm.client.github.api.GsonRepositoryCollaborator; -import org.sonar.alm.client.github.api.GsonRepositoryTeam; -import org.sonar.alm.client.github.security.AccessToken; +import org.sonar.auth.github.AppInstallationToken; +import org.sonar.auth.github.client.GithubApplicationClient; +import org.sonar.auth.github.GsonRepositoryCollaborator; +import org.sonar.auth.github.GsonRepositoryTeam; +import org.sonar.auth.github.security.AccessToken; import org.sonar.api.resources.Qualifiers; import org.sonar.api.web.UserRole; import org.sonar.alm.client.github.GithubPermissionConverter; -- 2.39.5