From e04f9e1a7abdbb0c90b48e1fd714947f9e8a4be8 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Fri, 9 Jun 2023 10:28:05 -0400 Subject: [PATCH 1/5] Cleanup openapi spec to match more strict measures Measures are applied through Insomnia on import, and while we could add exceptions, being more specific with the fields isn't a bad thing to do in this case. --- spec/openapi.yaml | 82 +++++++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/spec/openapi.yaml b/spec/openapi.yaml index c1b7bcec..906bde10 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -2,6 +2,10 @@ openapi: "3.1.0" info: version: 1.1.0 title: Eclipse Foundation Git ECA API + description: Collection of API endpoints used in the validation and management of external Git services, such as Gitlab and Github. + contact: + name: IT support + url: https://gitlab.eclipse.org/eclipsefdn/it/api/git-eca-rest-api/-/issues license: name: Eclipse Public License - 2.0 url: https://www.eclipse.org/legal/epl-2.0/ @@ -11,10 +15,15 @@ servers: tags: - name: ECA Validation description: Definitions in relation to the validation of Git commits through ECA signage + - name: Reports + description: Reports on metadata associated with Git systems managed by the Eclipse Foundation + - name: Integration Webhooks + description: Endpoints related to binding to external Git services through a webhook paths: /eca: post: + operationId: validate tags: - ECA Validation summary: ECA validation @@ -25,29 +34,36 @@ paths: schema: $ref: "#/components/schemas/ValidationRequest" responses: - 200: + "200": description: Success content: application/json: schema: $ref: "#/components/schemas/ValidationResponse" - 500: + "500": description: Error while retrieving data - /eca/status/{fingerprint}: + parameters: + - name: fingerprint + in: path + description: Unique ID for the request group + required: true + schema: + type: string get: + operationId: getCommitValidation tags: - - ECA Validation Status + - ECA Validation summary: Historic ECA validation status description: Returns a set of validation messages for the given unique fingerprint responses: - 200: + "200": description: Success content: application/json: schema: $ref: "#/components/schemas/CommitValidationStatuses" - 500: + "500": description: Error while retrieving data /eca/status/{fingerprint}/ui: @@ -59,34 +75,41 @@ paths: schema: type: string get: + operationId: getCommitValidationUI + tags: + - ECA Validation summary: Historic ECA validation status in a HTML format description: Returns an HTMl page containing validation messages responses: - 200: + "200": description: Success. An HTML page containing status info - 404: + "404": description: Not Found - 500: + "500": description: Error while retrieving data /eca/lookup: get: + operationId: getUserStatus + tags: + - ECA Validation summary: User status lookup description: Returns wether or not the user has a signed ECA responses: - 200: + "200": description: Success - 403: + "403": description: User exists with no ECA - 404: + "404": description: User not found - 500: + "500": description: Error while retrieving data /webhooks/github: post: + operationId: processGithubWebhook tags: - - Github validation processing + - Integration Webhooks summary: Github incoming hook event processing description: Process incoming pull request hook events from Github parameters: @@ -111,9 +134,9 @@ paths: schema: $ref: "#/components/schemas/GithubWebhookEvent" responses: - 200: + "200": description: Success - 500: + "500": description: Error while processing data /webhooks/github/revalidate/{fingerprint}: parameters: @@ -124,8 +147,9 @@ paths: schema: type: string post: + operationId: revalidateWebhookRequest tags: - - Github validation processing + - Integration Webhooks summary: Gitlab webhook revalidation request description: Process incoming system hooks from GitLab requestBody: @@ -134,20 +158,21 @@ paths: schema: $ref: '#/components/schemas/RevalidationRequest' responses: - 200: + "200": description: Success - 400: + "400": description: Bad request content: application/json: schema: $ref: "#/components/schemas/Error" - 404: + "404": description: Not found /webhooks/gitlab/system: post: + operationId: processGitlabHook tags: - - Gitlab system event processing + - Integration Webhooks summary: Gitlab event processing description: Process incoming system hooks from GitLab parameters: @@ -162,9 +187,9 @@ paths: schema: $ref: "#/components/schemas/SystemHook" responses: - 200: + "200": description: Success - 500: + "500": description: Error while processing data /reports/gitlab/private-projects: @@ -194,26 +219,27 @@ paths: schema: type: string get: + operationId: getPrivateProjectEvents tags: - - Private project event report + - Reports summary: Gitlab private project event report description: Returns list of private project events using desired filters responses: - 200: + "200": description: Success content: application/json: schema: $ref: "#/components/schemas/PrivateProjectEvents" - 400: + "400": description: Bad Request - invalid non-null prams content: application/json: schema: $ref: "#/components/schemas/Error" - 401: + "401": description: Unauthorized - invalid key - 500: + "500": description: Error while processing request components: -- GitLab From 3b63ee0260c26074e37aefa17e75ec4fc3ba35ea Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Fri, 9 Jun 2023 11:30:45 -0400 Subject: [PATCH 2/5] Iss #132 - Update API to use shared projects services In the API common lib, there is a new efservices module which provides access to the Accounts and Projects service in a standardized way. This PR removes most of the code associated with previous projects logic and keeps a helper to help with formating and filtering the data. This currently uses a snapshot version, so there will need to be a release for this code to go live and be stable. --- Jenkinsfile | 2 +- pom.xml | 9 +- .../git/eca/api/ProjectsAPI.java | 52 ---- .../git/eca/api/models/InterestGroupData.java | 64 ----- .../git/eca/api/models/Project.java | 194 -------------- .../git/eca/helper/CommitHelper.java | 2 +- .../git/eca/helper/ProjectHelper.java | 163 ++++++++++++ .../git/eca/resource/StatusResource.java | 6 +- .../git/eca/resource/ValidationResource.java | 7 +- .../git/eca/service/InterestGroupService.java | 30 --- .../git/eca/service/ProjectsService.java | 54 ---- .../git/eca/service/UserService.java | 2 +- .../git/eca/service/ValidationService.java | 2 +- .../eca/service/impl/CachedUserService.java | 4 +- .../impl/DefaultInterestGroupService.java | 64 ----- .../impl/DefaultValidationService.java | 11 +- .../impl/PaginationProjectsService.java | 213 ---------------- .../service/impl/CachedUserServiceTest.java | 6 +- .../impl/PaginationProjectsServiceTest.java | 92 ------- .../git/eca/test/api/MockProjectsAPI.java | 237 +++++++++++------- 20 files changed, 330 insertions(+), 884 deletions(-) delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java create mode 100644 src/main/java/org/eclipsefoundation/git/eca/helper/ProjectHelper.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java delete mode 100644 src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java diff --git a/Jenkinsfile b/Jenkinsfile index 53f1a610..8216d104 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -125,7 +125,7 @@ readTrusted 'pom.xml' sh 'make generate-spec' withCredentials([string(credentialsId: 'sonarcloud-token-git-eca-rest-api', variable: 'SONAR_TOKEN')]) { - sh 'mvn clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -B -Dsonar.login=${SONAR_TOKEN}' + sh 'mvn org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.login=${SONAR_TOKEN} -Dmaven.test.skip=true' } stash name: "target", includes: "target/**/*" } diff --git a/pom.xml b/pom.xml index fd2d12a4..e5500bc2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <artifactId>git-eca</artifactId> <version>1.1.0</version> <properties> - <eclipse-api-version>0.7.4</eclipse-api-version> + <eclipse-api-version>0.7.6-SNAPSHOT</eclipse-api-version> <compiler-plugin.version>3.8.1</compiler-plugin.version> <maven.compiler.parameters>true</maven.compiler.parameters> <maven.compiler.source>11</maven.compiler.source> @@ -14,7 +14,7 @@ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id> <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id> - <quarkus.platform.version>2.14.2.Final</quarkus.platform.version> + <quarkus.platform.version>2.16.3.Final</quarkus.platform.version> <surefire-plugin.version>2.22.1</surefire-plugin.version> <auto-value.version>1.8.2</auto-value.version> <org.mapstruct.version>1.4.1.Final</org.mapstruct.version> @@ -63,6 +63,11 @@ <artifactId>quarkus-persistence</artifactId> <version>${eclipse-api-version}</version> </dependency> + <dependency> + <groupId>org.eclipsefoundation</groupId> + <artifactId>quarkus-efservices</artifactId> + <version>${eclipse-api-version}</version> + </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy</artifactId> diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java deleted file mode 100644 index 9e951212..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java +++ /dev/null @@ -1,52 +0,0 @@ -/********************************************************************* -* Copyright (c) 2022 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.api; - -import javax.enterprise.context.ApplicationScoped; -import javax.ws.rs.BeanParam; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Response; - -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; -import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters; -import org.jboss.resteasy.annotations.GZIP; - -/** - * Interface for interacting with the PMI Projects API. Used to link Git - * repos/projects with an Eclipse project to validate committer access. - * - * @author Martin Lowe - * - */ -@ApplicationScoped -@Path("/api") -@RegisterRestClient -@GZIP -public interface ProjectsAPI { - - /** - * Retrieves all projects with the given repo URL. - * - * @param repoUrl the target repos URL - * @return a list of Eclipse Foundation projects. - */ - @GET - @Path("projects") - Response getProjects(@BeanParam BaseAPIParameters baseParams); - - @GET - @Path("interest-groups") - @Produces("application/json") - Response getInterestGroups(@BeanParam BaseAPIParameters params); -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java deleted file mode 100644 index e70f8306..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.eclipsefoundation.git.eca.api.models; - -import java.util.List; - -import org.eclipsefoundation.git.eca.api.models.Project.GitlabProject; -import org.eclipsefoundation.git.eca.api.models.Project.User; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import com.google.auto.value.AutoValue; - -@AutoValue -@JsonDeserialize(builder = AutoValue_InterestGroupData.Builder.class) -public abstract class InterestGroupData { - public abstract String getId(); - public abstract String getTitle(); - public abstract String getLogo(); - public abstract String getState(); - public abstract String getProjectId(); - public abstract Descriptor getDescription(); - public abstract Descriptor getScope(); - public abstract List<User> getLeads(); - public abstract List<User> getParticipants(); - public abstract GitlabProject getGitlab(); - - public static Builder builder() { - return new AutoValue_InterestGroupData.Builder(); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setId(String id); - public abstract Builder setTitle(String title); - public abstract Builder setGitlab(GitlabProject gitlab); - public abstract Builder setLogo(String logo); - public abstract Builder setState(String state); - public abstract Builder setProjectId(String foundationdbProjectId); - public abstract Builder setDescription(Descriptor description); - public abstract Builder setScope(Descriptor scope); - public abstract Builder setLeads(List<User> leads); - public abstract Builder setParticipants(List<User> participants); - public abstract InterestGroupData build(); - } - - @AutoValue - @JsonDeserialize(builder = AutoValue_InterestGroupData_Descriptor.Builder.class) - public abstract static class Descriptor { - public abstract String getSummary(); - public abstract String getFull(); - - public static Builder builder() { - return new AutoValue_InterestGroupData_Descriptor.Builder(); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setSummary(String summary); - public abstract Builder setFull(String full); - public abstract Descriptor build(); - } - } -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java deleted file mode 100644 index 7b30c1be..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java +++ /dev/null @@ -1,194 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.api.models; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import com.google.auto.value.AutoValue; -import com.google.auto.value.extension.memoized.Memoized; - -/** - * Represents a project in the Eclipse API, along with the users and repos that exist within the context of the project. - * - * @author Martin Lowe - * - */ -@AutoValue -@JsonDeserialize(builder = $AutoValue_Project.Builder.class) -public abstract class Project { - public abstract String getProjectId(); - - public abstract String getName(); - - public abstract List<User> getCommitters(); - - public abstract List<User> getProjectLeads(); - - @Nullable - public abstract List<Repo> getRepos(); - - public abstract List<Repo> getGitlabRepos(); - - public abstract List<Repo> getGithubRepos(); - - public abstract List<Repo> getGerritRepos(); - - public abstract Object getSpecProjectWorkingGroup(); - - public abstract GitlabProject getGitlab(); - - public abstract GithubProject getGithub(); - - @Nullable - @Memoized - public String getSpecWorkingGroup() { - // stored as map as empty returns an array instead of a map - Object specProjectWorkingGroup = getSpecProjectWorkingGroup(); - if (specProjectWorkingGroup instanceof Map) { - // we checked in line above that the map exists, so we can safely cast it - @SuppressWarnings("unchecked") - Object raw = ((Map<Object, Object>) specProjectWorkingGroup).get("id"); - if (raw instanceof String) { - return (String) raw; - } - } - return null; - } - - public static Builder builder() { - // adds empty lists as default values - return new AutoValue_Project.Builder() - .setRepos(new ArrayList<>()) - .setCommitters(new ArrayList<>()) - .setGithubRepos(new ArrayList<>()) - .setGitlabRepos(new ArrayList<>()) - .setGerritRepos(new ArrayList<>()); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setProjectId(String projectId); - - public abstract Builder setName(String name); - - public abstract Builder setProjectLeads(List<User> projectLeads); - - public abstract Builder setCommitters(List<User> committers); - - public abstract Builder setRepos(@Nullable List<Repo> repos); - - public abstract Builder setGitlabRepos(List<Repo> gitlabRepos); - - public abstract Builder setGithubRepos(List<Repo> githubRepos); - - public abstract Builder setGerritRepos(List<Repo> gerritRepos); - - public abstract Builder setSpecProjectWorkingGroup(Object specProjectWorkingGroup); - - public abstract Builder setGitlab(GitlabProject gitlab); - - public abstract Builder setGithub(GithubProject github); - - public abstract Project build(); - } - - @AutoValue - @JsonDeserialize(builder = AutoValue_Project_User.Builder.class) - public abstract static class User { - public abstract String getUsername(); - - public abstract String getUrl(); - - public static Builder builder() { - return new AutoValue_Project_User.Builder(); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setUsername(String username); - - public abstract Builder setUrl(String url); - - public abstract User build(); - } - } - - @AutoValue - @JsonDeserialize(builder = AutoValue_Project_GitlabProject.Builder.class) - public abstract static class GitlabProject { - public abstract String getProjectGroup(); - - public abstract List<String> getIgnoredSubGroups(); - - public static Builder builder() { - return new AutoValue_Project_GitlabProject.Builder(); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setProjectGroup(String projectGroup); - - public abstract Builder setIgnoredSubGroups(List<String> ignoredSubGroups); - - public abstract GitlabProject build(); - } - } - - @AutoValue - @JsonDeserialize(builder = AutoValue_Project_GithubProject.Builder.class) - public abstract static class GithubProject { - public abstract String getOrg(); - - public abstract List<String> getIgnoredRepos(); - - public static Builder builder() { - return new AutoValue_Project_GithubProject.Builder(); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setOrg(String org); - - public abstract Builder setIgnoredRepos(List<String> ignoredRepos); - - public abstract GithubProject build(); - } - } - - /** - * Does not use autovalue as the value should be mutable. - * - * @author Martin Lowe - * - */ - public static class Repo { - private String url; - - public String getUrl() { - return this.url; - } - - public void setUrl(String url) { - this.url = url; - } - } -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java b/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java index 0650a2e8..eaa36924 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java +++ b/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java @@ -16,7 +16,7 @@ import java.util.stream.Collectors; import javax.ws.rs.core.MultivaluedMap; -import org.eclipsefoundation.git.eca.api.models.Project; +import org.eclipsefoundation.efservices.api.models.Project; import org.eclipsefoundation.git.eca.model.Commit; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames; diff --git a/src/main/java/org/eclipsefoundation/git/eca/helper/ProjectHelper.java b/src/main/java/org/eclipsefoundation/git/eca/helper/ProjectHelper.java new file mode 100644 index 00000000..49c85eb2 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/helper/ProjectHelper.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2023 Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * Author: Martin Lowe <martin.lowe@eclipse-foundation.org> + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.git.eca.helper; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.apache.commons.lang3.StringUtils; +import org.eclipsefoundation.core.service.CachingService; +import org.eclipsefoundation.efservices.api.models.InterestGroup; +import org.eclipsefoundation.efservices.api.models.Project; +import org.eclipsefoundation.efservices.api.models.Project.GithubProject; +import org.eclipsefoundation.efservices.api.models.Project.ProjectParticipant; +import org.eclipsefoundation.efservices.services.ProjectService; +import org.eclipsefoundation.git.eca.model.ValidationRequest; +import org.eclipsefoundation.git.eca.namespace.ProviderType; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helps manage projects by providing filters on top of the generic service as well as operations like adapting interest + * groups to projects for easier processing. + * + * @author Martin Lowe + * + */ +@ApplicationScoped +public final class ProjectHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(ProjectHelper.class); + + @Inject + CachingService cache; + @Inject + ProjectService projects; + + public List<Project> retrieveProjectsForRequest(ValidationRequest req) { + String repoUrl = req.getRepoUrl().getPath(); + if (repoUrl == null) { + LOGGER.warn("Can not match null repo URL to projects"); + return Collections.emptyList(); + } + return retrieveProjectsForRepoURL(repoUrl, req.getProvider()); + } + + public List<Project> retrieveProjectsForRepoURL(String repoUrl, ProviderType provider) { + if (repoUrl == null) { + LOGGER.warn("Can not match null repo URL to projects"); + return Collections.emptyList(); + } + // check for all projects that make use of the given repo + List<Project> availableProjects = getProjects(); + if (availableProjects.isEmpty()) { + LOGGER.warn("Could not find any projects to match against"); + return Collections.emptyList(); + } + LOGGER.debug("Checking projects for repos that end with: {}", repoUrl); + + String projectNamespace = URI.create(repoUrl).getPath().substring(1).toLowerCase(); + // filter the projects based on the repo URL. At least one repo in project must + // match the repo URL to be valid + switch (provider) { + case GITLAB: + return availableProjects + .stream() + .filter(p -> projectNamespace.startsWith(p.getGitlab().getProjectGroup() + "/") + && p.getGitlab().getIgnoredSubGroups().stream().noneMatch(sg -> projectNamespace.startsWith(sg + "/"))) + .collect(Collectors.toList()); + case GITHUB: + return availableProjects + .stream() + .filter(p -> p.getGithubRepos().stream().anyMatch(re -> re.getUrl() != null && re.getUrl().endsWith(repoUrl)) + || (StringUtils.isNotBlank(p.getGithub().getOrg()) && projectNamespace.startsWith(p.getGithub().getOrg()) + && p.getGithub().getIgnoredRepos().stream().noneMatch(repoUrl::endsWith))) + .collect(Collectors.toList()); + case GERRIT: + return availableProjects + .stream() + .filter(p -> p.getGerritRepos().stream().anyMatch(re -> re.getUrl() != null && re.getUrl().endsWith(repoUrl))) + .collect(Collectors.toList()); + default: + return Collections.emptyList(); + } + } + + /** + * Retrieve cached and adapted projects list to avoid having to create all interest group adaptations on every request. + * + * @return list of available projects or empty list if none found. + */ + public List<Project> getProjects() { + return cache.get("all-combined", new MultivaluedMapImpl<>(), Project.class, () -> { + List<Project> availableProjects = projects.getAllProjects(); + availableProjects.addAll(adaptInterestGroups(projects.getAllInterestGroups())); + return availableProjects; + }).orElseGet(Collections::emptyList); + } + + private List<Project> adaptInterestGroups(List<InterestGroup> igs) { + return igs + .stream() + .map(ig -> Project + .builder() + .setProjectId(ig.getProjectId()) + .setGerritRepos(Collections.emptyList()) + .setGithubRepos(Collections.emptyList()) + .setGitlabRepos(Collections.emptyList()) + .setGitlab(ig.getGitlab()) + .setCommitters(ig + .getParticipants() + .stream() + .map(p -> ProjectParticipant + .builder() + .setFullName(p.getFullName()) + .setUrl(p.getUrl()) + .setUsername(p.getUsername()) + .build()) + .collect(Collectors.toList())) + .setProjectLeads(ig + .getLeads() + .stream() + .map(p -> ProjectParticipant + .builder() + .setFullName(p.getFullName()) + .setUrl(p.getUrl()) + .setUsername(p.getUsername()) + .build()) + .collect(Collectors.toList())) + .setContributors(Collections.emptyList()) + .setShortProjectId(ig.getShortProjectId()) + .setSlsaLevel("") + .setSummary("") + .setWebsiteUrl("") + .setWebsiteRepo(Collections.emptyList()) + .setGitlab(ig.getGitlab()) + .setGithub(GithubProject.builder().setOrg("").setIgnoredRepos(Collections.emptyList()).build()) + .setWorkingGroups(Collections.emptyList()) + .setIndustryCollaborations(Collections.emptyList()) + .setReleases(Collections.emptyList()) + .setTopLevelProject("") + .setUrl("") + .setLogo(ig.getLogo()) + .setTags(Collections.emptyList()) + .setName(ig.getTitle()) + .setSpecProjectWorkingGroup(Collections.emptyMap()) + .build()) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java index b2bd8371..8c612938 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java @@ -27,14 +27,14 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; +import org.eclipsefoundation.efservices.api.models.Project; import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest; import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest; -import org.eclipsefoundation.git.eca.api.models.Project; import org.eclipsefoundation.git.eca.dto.CommitValidationStatus; import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking; +import org.eclipsefoundation.git.eca.helper.ProjectHelper; import org.eclipsefoundation.git.eca.model.Commit; import org.eclipsefoundation.git.eca.model.ValidationRequest; -import org.eclipsefoundation.git.eca.service.ProjectsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +52,7 @@ public class StatusResource extends GithubAdjacentResource { private static final Logger LOGGER = LoggerFactory.getLogger(StatusResource.class); @Inject - ProjectsService projects; + ProjectHelper projects; // Qute templates, generates UI status page @Location("simple_fingerprint_ui") diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java index 90df077e..a567bcda 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java @@ -29,11 +29,10 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipsefoundation.core.model.RequestWrapper; import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.git.eca.api.models.EclipseUser; +import org.eclipsefoundation.git.eca.helper.ProjectHelper; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.model.ValidationResponse; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; -import org.eclipsefoundation.git.eca.service.InterestGroupService; -import org.eclipsefoundation.git.eca.service.ProjectsService; import org.eclipsefoundation.git.eca.service.UserService; import org.eclipsefoundation.git.eca.service.ValidationService; import org.jboss.resteasy.annotations.jaxrs.QueryParam; @@ -67,13 +66,11 @@ public class ValidationResource { @Inject CachingService cache; @Inject - ProjectsService projects; + ProjectHelper projects; @Inject UserService users; @Inject ValidationService validation; - @Inject - InterestGroupService ig; /** * Consuming a JSON request, this method will validate all passed commits, using the repo URL and the repository diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java b/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java deleted file mode 100644 index 8c2d182b..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.eclipsefoundation.git.eca.service; - -import java.util.List; - -import org.eclipsefoundation.git.eca.api.models.InterestGroupData; -import org.eclipsefoundation.git.eca.api.models.Project; - -/** - * Service for retrieving and interacting with interest groups. - * - * @author Martin Lowe - * - */ -public interface InterestGroupService { - - /** - * Retrieve all available interest groups. - * - * @return list of all available interest groups - */ - List<InterestGroupData> getInterestGroups(); - - /** - * Converts interest groups into projects for processing downstream. - * - * @param igs the interest groups to convert - * @return the converted interest groups - */ - List<Project> adaptInterestGroups(List<InterestGroupData> igs); -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java b/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java deleted file mode 100644 index 54c2199f..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java +++ /dev/null @@ -1,54 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.service; - -import java.util.List; - -import org.eclipsefoundation.git.eca.api.models.Project; -import org.eclipsefoundation.git.eca.model.ValidationRequest; -import org.eclipsefoundation.git.eca.namespace.ProviderType; - -/** - * Intermediate layer between resource and API layers that handles retrieval of all projects and caching of that data - * for availability purposes. - * - * @author Martin Lowe - * - */ -public interface ProjectsService { - - /** - * Retrieves all currently available projects from cache if available, otherwise going to API to retrieve a fresh copy - * of the data. - * - * @return list of projects available from API. - */ - List<Project> getProjects(); - - /** - * Retrieves projects valid for the current request, or an empty list if no data or matching project repos could be - * found. - * - * @param req the current request - * @return list of matching projects for the current request, or an empty list if none found. - */ - List<Project> retrieveProjectsForRequest(ValidationRequest req); - - /** - * Retrieves projects for given provider, using the repo URL to match to a stored repository. - * - * @param repoUrl the repo URL to match - * @param provider the provider that is being served for the request. - * @return a list of matching projects, or an empty list if none are found. - */ - List<Project> retrieveProjectsForRepoURL(String repoUrl, ProviderType provider); -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java index 06c01484..1134f1d9 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java @@ -13,8 +13,8 @@ package org.eclipsefoundation.git.eca.service; import java.util.List; +import org.eclipsefoundation.efservices.api.models.Project; import org.eclipsefoundation.git.eca.api.models.EclipseUser; -import org.eclipsefoundation.git.eca.api.models.Project; public interface UserService { diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java b/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java index 1f441ca0..b26a3fb8 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java @@ -16,7 +16,7 @@ import java.security.NoSuchAlgorithmException; import java.util.List; import org.eclipsefoundation.core.model.RequestWrapper; -import org.eclipsefoundation.git.eca.api.models.Project; +import org.eclipsefoundation.efservices.api.models.Project; import org.eclipsefoundation.git.eca.dto.CommitValidationStatus; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.model.ValidationResponse; diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java index 45ad5017..1d576e0d 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java @@ -14,8 +14,8 @@ package org.eclipsefoundation.git.eca.service.impl; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Optional; import java.util.Map.Entry; +import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -28,10 +28,10 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.eclipsefoundation.core.service.CachingService; +import org.eclipsefoundation.efservices.api.models.Project; import org.eclipsefoundation.git.eca.api.AccountsAPI; import org.eclipsefoundation.git.eca.api.BotsAPI; import org.eclipsefoundation.git.eca.api.models.EclipseUser; -import org.eclipsefoundation.git.eca.api.models.Project; import org.eclipsefoundation.git.eca.service.OAuthService; import org.eclipsefoundation.git.eca.service.UserService; import org.jboss.resteasy.specimpl.MultivaluedMapImpl; diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java deleted file mode 100644 index c56f56da..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.eclipsefoundation.git.eca.service.impl; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; - -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.eclipsefoundation.core.service.APIMiddleware; -import org.eclipsefoundation.core.service.CachingService; -import org.eclipsefoundation.git.eca.api.ProjectsAPI; -import org.eclipsefoundation.git.eca.api.models.InterestGroupData; -import org.eclipsefoundation.git.eca.api.models.Project; -import org.eclipsefoundation.git.eca.service.InterestGroupService; -import org.jboss.resteasy.specimpl.MultivaluedMapImpl; - -/** - * Default implementation of interest group service. - * - * @author Martin Lowe - */ -@ApplicationScoped -public class DefaultInterestGroupService implements InterestGroupService { - - @Inject - APIMiddleware middleware; - @Inject - CachingService cache; - - @Inject - @RestClient - ProjectsAPI api; - - @Override - public List<InterestGroupData> getInterestGroups() { - return cache - .get("all", new MultivaluedMapImpl<>(), InterestGroupData.class, - () -> middleware.getAll(api::getInterestGroups, InterestGroupData.class)) - .orElse(Collections.emptyList()); - } - - @Override - public List<Project> adaptInterestGroups(List<InterestGroupData> igs) { - return igs - .stream() - .map(ig -> Project - .builder() - .setProjectId(ig.getProjectId()) - .setGerritRepos(Collections.emptyList()) - .setGithubRepos(Collections.emptyList()) - .setGitlabRepos(Collections.emptyList()) - .setRepos(Collections.emptyList()) - .setGitlab(ig.getGitlab()) - .setCommitters(ig.getParticipants()) - .setProjectLeads(ig.getLeads()) - .setName(ig.getTitle()) - .setSpecProjectWorkingGroup(Collections.emptyMap()) - .build()) - .collect(Collectors.toList()); - } - -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java index 647252e0..7c45cd78 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java @@ -28,20 +28,19 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipsefoundation.core.helper.DateTimeHelper; import org.eclipsefoundation.core.model.RequestWrapper; import org.eclipsefoundation.core.service.CachingService; +import org.eclipsefoundation.efservices.api.models.Project; import org.eclipsefoundation.git.eca.api.models.EclipseUser; -import org.eclipsefoundation.git.eca.api.models.Project; import org.eclipsefoundation.git.eca.dto.CommitValidationMessage; import org.eclipsefoundation.git.eca.dto.CommitValidationStatus; import org.eclipsefoundation.git.eca.dto.CommitValidationStatusGrouping; import org.eclipsefoundation.git.eca.helper.CommitHelper; +import org.eclipsefoundation.git.eca.helper.ProjectHelper; import org.eclipsefoundation.git.eca.model.Commit; import org.eclipsefoundation.git.eca.model.GitUser; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.model.ValidationResponse; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames; -import org.eclipsefoundation.git.eca.service.InterestGroupService; -import org.eclipsefoundation.git.eca.service.ProjectsService; import org.eclipsefoundation.git.eca.service.UserService; import org.eclipsefoundation.git.eca.service.ValidationService; import org.eclipsefoundation.persistence.dao.PersistenceDao; @@ -66,9 +65,7 @@ public class DefaultValidationService implements ValidationService { List<String> allowListUsers; @Inject - ProjectsService projects; - @Inject - InterestGroupService ig; + ProjectHelper projects; @Inject UserService users; @@ -386,7 +383,7 @@ public class DefaultValidationService implements ValidationService { if (p.getCommitters().stream().anyMatch(u -> u.getUsername().equals(user.getName()))) { // check if the current project is a committer project, and if the user can // commit to specs - if (p.getSpecWorkingGroup() != null && !user.getECA().getCanContributeSpecProject()) { + if (p.getSpecWorkingGroup().isPresent() && !user.getECA().getCanContributeSpecProject()) { // set error + update response status r .addError(hash, String diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java deleted file mode 100644 index 9b48cac9..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java +++ /dev/null @@ -1,213 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* Zachary Sabourin <zachary.sabourin@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.service.impl; - -import java.net.URI; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import javax.annotation.PostConstruct; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; - -import org.apache.commons.lang3.StringUtils; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipse.microprofile.context.ManagedExecutor; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.eclipsefoundation.core.service.APIMiddleware; -import org.eclipsefoundation.core.service.CachingService; -import org.eclipsefoundation.git.eca.api.ProjectsAPI; -import org.eclipsefoundation.git.eca.api.models.Project; -import org.eclipsefoundation.git.eca.model.ValidationRequest; -import org.eclipsefoundation.git.eca.namespace.ProviderType; -import org.eclipsefoundation.git.eca.service.InterestGroupService; -import org.eclipsefoundation.git.eca.service.ProjectsService; -import org.jboss.resteasy.specimpl.MultivaluedMapImpl; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListenableFutureTask; - -import io.quarkus.runtime.Startup; - -/** - * Projects service implementation that handles pagination of data manually, as well as makes use of a loading cache to - * have data be always available with as little latency to the user as possible. - * - * @author Martin Lowe - * @author Zachary Sabourin - */ -@Startup -@ApplicationScoped -public class PaginationProjectsService implements ProjectsService { - private static final Logger LOGGER = LoggerFactory.getLogger(PaginationProjectsService.class); - - @ConfigProperty(name = "eclipse.projects.precache.enabled", defaultValue = "true") - boolean isEnabled; - @ConfigProperty(name = "cache.pagination.refresh-frequency-seconds", defaultValue = "3600") - long refreshAfterWrite; - - @Inject - @RestClient - ProjectsAPI projects; - @Inject - InterestGroupService ig; - @Inject - CachingService cache; - @Inject - APIMiddleware middleware; - - @Inject - ManagedExecutor exec; - // this class has a separate cache as this data is long to load and should be - // always available. - LoadingCache<String, List<Project>> internalCache; - - /** - * Initializes the internal loader cache and pre-populates the data with the one available key. If more than one key is - * used, eviction of previous results will happen and create degraded performance. - */ - @PostConstruct - public void init() { - // set up the internal cache - this.internalCache = CacheBuilder - .newBuilder() - .maximumSize(1) - .refreshAfterWrite(refreshAfterWrite, TimeUnit.SECONDS) - .build(new CacheLoader<String, List<Project>>() { - @Override - public List<Project> load(String key) throws Exception { - return getProjectsInternal(); - } - - /** - * Implementation required for refreshAfterRewrite to be async rather than sync and blocking while awaiting for - * expensive reload to complete. - */ - @Override - public ListenableFuture<List<Project>> reload(String key, List<Project> oldValue) throws Exception { - ListenableFutureTask<List<Project>> task = ListenableFutureTask.create(() -> { - LOGGER.debug("Retrieving new project data async"); - List<Project> newProjects = oldValue; - try { - newProjects = getProjectsInternal(); - } catch (Exception e) { - LOGGER.error("Error while reloading internal projects data, data will be stale for current cycle.", e); - } - LOGGER.debug("Done refreshing project values"); - return newProjects; - }); - // run the task using the Quarkus managed executor - exec.execute(task); - return task; - } - }); - - if (isEnabled) { - // pre-cache the projects to reduce load time for other users - LOGGER.debug("Starting pre-cache of projects"); - if (getProjects() == null) { - LOGGER.warn("Unable to populate pre-cache for Eclipse projects. Calls may experience degraded performance."); - } - LOGGER.debug("Completed pre-cache of projects assets"); - } - } - - @Override - public List<Project> getProjects() { - try { - return internalCache.get("projects"); - } catch (ExecutionException e) { - throw new RuntimeException("Could not load Eclipse projects", e); - } - } - - @Override - public List<Project> retrieveProjectsForRequest(ValidationRequest req) { - String repoUrl = req.getRepoUrl().getPath(); - if (repoUrl == null) { - LOGGER.warn("Can not match null repo URL to projects"); - return Collections.emptyList(); - } - return retrieveProjectsForRepoURL(repoUrl, req.getProvider()); - } - - @Override - public List<Project> retrieveProjectsForRepoURL(String repoUrl, ProviderType provider) { - if (repoUrl == null) { - LOGGER.warn("Can not match null repo URL to projects"); - return Collections.emptyList(); - } - // check for all projects that make use of the given repo - List<Project> availableProjects = getProjects(); - availableProjects - .addAll(cache - .get("all", new MultivaluedMapImpl<>(), Project.class, () -> ig.adaptInterestGroups(ig.getInterestGroups())) - .orElse(Collections.emptyList())); - if (availableProjects.isEmpty()) { - LOGGER.warn("Could not find any projects to match against"); - return Collections.emptyList(); - } - LOGGER.debug("Checking projects for repos that end with: {}", repoUrl); - - String projectNamespace = URI.create(repoUrl).getPath().substring(1).toLowerCase(); - // filter the projects based on the repo URL. At least one repo in project must - // match the repo URL to be valid - switch (provider) { - case GITLAB: - return availableProjects - .stream() - .filter(p -> projectNamespace.startsWith(p.getGitlab().getProjectGroup() + "/") - && p.getGitlab().getIgnoredSubGroups().stream().noneMatch(sg -> projectNamespace.startsWith(sg + "/"))) - .collect(Collectors.toList()); - case GITHUB: - return availableProjects - .stream() - .filter(p -> p.getGithubRepos().stream().anyMatch(re -> re.getUrl() != null && re.getUrl().endsWith(repoUrl)) - || (StringUtils.isNotBlank(p.getGithub().getOrg()) && projectNamespace.startsWith(p.getGithub().getOrg()) - && p.getGithub().getIgnoredRepos().stream().noneMatch(repoUrl::endsWith))) - .collect(Collectors.toList()); - case GERRIT: - return availableProjects - .stream() - .filter(p -> p.getGerritRepos().stream().anyMatch(re -> re.getUrl() != null && re.getUrl().endsWith(repoUrl))) - .collect(Collectors.toList()); - default: - return Collections.emptyList(); - } - } - - /** - * Logic for retrieving projects from API. - * - * @return list of projects for the cache - */ - private List<Project> getProjectsInternal() { - - return middleware.getAll(params -> projects.getProjects(params), Project.class).stream().map(proj -> { - proj.getGerritRepos().forEach(repo -> { - if (repo.getUrl().endsWith(".git")) { - repo.setUrl(repo.getUrl().substring(0, repo.getUrl().length() - 4)); - } - }); - return proj; - }).collect(Collectors.toList()); - } -} diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java index 231b401c..b0c25a00 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java @@ -18,9 +18,9 @@ import java.util.Optional; import javax.inject.Inject; +import org.eclipsefoundation.efservices.api.models.Project; import org.eclipsefoundation.git.eca.api.models.EclipseUser; -import org.eclipsefoundation.git.eca.api.models.Project; -import org.eclipsefoundation.git.eca.service.ProjectsService; +import org.eclipsefoundation.git.eca.helper.ProjectHelper; import org.eclipsefoundation.git.eca.service.UserService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -40,7 +40,7 @@ class CachedUserServiceTest { @Inject UserService users; @Inject - ProjectsService projects; + ProjectHelper projects; @Test void getUser_success() { diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java deleted file mode 100644 index 3ebbabce..00000000 --- a/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.service.impl; - -import java.util.List; -import java.util.stream.Collectors; - -import javax.inject.Inject; - -import org.eclipsefoundation.git.eca.api.models.Project; -import org.eclipsefoundation.git.eca.api.models.Project.Repo; -import org.eclipsefoundation.git.eca.service.ProjectsService; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import io.quarkus.test.junit.QuarkusTest; - -@QuarkusTest -class PaginationProjectsServiceTest { - // get the projects service - @Inject - ProjectsService ps; - - @Test - void validateGerritUrlScrubbed() { - // get all projects - List<Project> projectsAll = ps.getProjects(); - // get projects that have gerrit repos - List<Project> projs = projectsAll - .stream() - .filter(p -> !p.getGerritRepos().isEmpty()) - .collect(Collectors.toList()); - // for all repos, check that none end with .git (this doesn't account for - // .git.git, but I assume that would be an entry error) - for (Project p : projs) { - for (Repo r : p.getGerritRepos()) { - Assertions.assertFalse(r.getUrl().endsWith(".git"), "Expected no URLs to end with '.git'"); - } - } - } - - @Test - void validateGithubUrlNotScrubbed() { - // get all projects - List<Project> projectsAll = ps.getProjects(); - // get projects that have github repos - List<Project> projs = projectsAll - .stream() - .filter(p -> !p.getGithubRepos().isEmpty()) - .collect(Collectors.toList()); - // for all repos, check that at least one ends with .git - boolean foundGitSuffix = false; - for (Project p : projs) { - for (Repo r : p.getGithubRepos()) { - if (r.getUrl().endsWith(".git")) { - foundGitSuffix = true; - } - } - } - Assertions.assertTrue(foundGitSuffix, "Expected a URL to end with '.git'"); - } - - @Test - void validateGitlabUrlNotScrubbed() { - // get all projects - List<Project> projectsAll = ps.getProjects(); - // get projects that have gitlab repos - List<Project> projs = projectsAll - .stream() - .filter(p -> !p.getGitlabRepos().isEmpty()) - .collect(Collectors.toList()); - // for all repos, check that at least one ends with .git - boolean foundGitSuffix = false; - for (Project p : projs) { - for (Repo r : p.getGitlabRepos()) { - if (r.getUrl().endsWith(".git")) { - foundGitSuffix = true; - } - } - } - Assertions.assertTrue(foundGitSuffix, "Expected a URL to end with '.git'"); - } -} diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java index 0528b1ea..4be050fc 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java @@ -1,11 +1,11 @@ /********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. +* Copyright (c) 2023 Eclipse Foundation. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> +* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org> * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ @@ -14,24 +14,26 @@ package org.eclipsefoundation.git.eca.test.api; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; -import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.core.Response; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters; -import org.eclipsefoundation.git.eca.api.ProjectsAPI; -import org.eclipsefoundation.git.eca.api.models.InterestGroupData; -import org.eclipsefoundation.git.eca.api.models.Project; -import org.eclipsefoundation.git.eca.api.models.InterestGroupData.Descriptor; -import org.eclipsefoundation.git.eca.api.models.Project.GithubProject; -import org.eclipsefoundation.git.eca.api.models.Project.GitlabProject; -import org.eclipsefoundation.git.eca.api.models.Project.Repo; -import org.eclipsefoundation.git.eca.api.models.Project.User; +import org.eclipsefoundation.efservices.api.ProjectsAPI; +import org.eclipsefoundation.efservices.api.models.GitlabProject; +import org.eclipsefoundation.efservices.api.models.InterestGroup; +import org.eclipsefoundation.efservices.api.models.InterestGroup.Descriptor; +import org.eclipsefoundation.efservices.api.models.InterestGroup.InterestGroupParticipant; +import org.eclipsefoundation.efservices.api.models.InterestGroup.Organization; +import org.eclipsefoundation.efservices.api.models.InterestGroup.Resource; +import org.eclipsefoundation.efservices.api.models.Project; +import org.eclipsefoundation.efservices.api.models.Project.GithubProject; +import org.eclipsefoundation.efservices.api.models.Project.ProjectParticipant; +import org.eclipsefoundation.efservices.api.models.Project.Repo; import io.quarkus.test.Mock; @@ -40,124 +42,169 @@ import io.quarkus.test.Mock; @ApplicationScoped public class MockProjectsAPI implements ProjectsAPI { - private List<Project> src; + private List<Project> projects; - @PostConstruct - public void build() { - this.src = new ArrayList<>(); + public MockProjectsAPI() { + this.projects = new ArrayList<>(); // sample repos - Repo r1 = new Repo(); - r1.setUrl("http://www.github.com/eclipsefdn/sample"); - Repo r2 = new Repo(); - r2.setUrl("http://www.github.com/eclipsefdn/test"); - Repo r3 = new Repo(); - r3.setUrl("http://www.github.com/eclipsefdn/prototype.git"); - Repo r5 = new Repo(); - r5.setUrl("/gitroot/sample/gerrit.project.git"); - Repo r6 = new Repo(); - r6.setUrl("/gitroot/sample/gerrit.other-project"); - Repo r7 = new Repo(); - r7.setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.git"); - Repo r8 = new Repo(); - r8.setUrl("https://gitlab.eclipse.org/eclipse/dash-second/dash.handbook.test"); - - // sample users, correlates to users in Mock projects API - User u1 = User.builder().setUrl("").setUsername("da_wizz").build(); - User u2 = User.builder().setUrl("").setUsername("grunter").build(); - - // projects - Project p1 = Project - .builder() + Repo r1 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/sample").build(); + Repo r2 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/test").build(); + Repo r3 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/prototype.git").build(); + Repo r4 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/tck-proto").build(); + Repo r5 = Repo.builder().setUrl("/gitroot/sample/gerrit.project.git").build(); + Repo r6 = Repo.builder().setUrl("/gitroot/sample/gerrit.other-project").build(); + Repo r7 = Repo.builder().setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.git").build(); + Repo r8 = Repo.builder().setUrl("https://gitlab.eclipse.org/eclipse/dash-second/dash.handbook.test").build(); + + // sample users + ProjectParticipant u1 = ProjectParticipant.builder().setUrl("").setUsername("da_wizz").setFullName("da_wizz") + .build(); + ProjectParticipant u2 = ProjectParticipant.builder().setUrl("").setUsername("grunter").setFullName("grunter") + .build(); + + this.projects.add(Project.builder() .setName("Sample project") .setProjectId("sample.proj") - .setSpecProjectWorkingGroup(Collections.emptyList()) + .setSpecProjectWorkingGroup(Collections.emptyMap()) .setGithubRepos(Arrays.asList(r1, r2)) .setGerritRepos(Arrays.asList(r5)) .setCommitters(Arrays.asList(u1, u2)) .setProjectLeads(Collections.emptyList()) - .setGitlab(GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()).setProjectGroup("").build()) - .setGithub(GithubProject.builder().setIgnoredRepos(Collections.emptyList()).setOrg("").build()) - .build(); - src.add(p1); + .setGitlab(GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()).setProjectGroup("") + .build()) + .setShortProjectId("sample.proj") + .setSummary("summary") + .setUrl("project.url.com") + .setWebsiteUrl("someproject.com") + .setWebsiteRepo(Collections.emptyList()) + .setLogo("logoUrl.com") + .setTags(Collections.emptyList()) + .setGithub(GithubProject.builder() + .setOrg("") + .setIgnoredRepos(Collections.emptyList()).build()) + .setGitlabRepos(Collections.emptyList()) + .setContributors(Collections.emptyList()) + .setWorkingGroups(Collections.emptyList()) + .setIndustryCollaborations(Collections.emptyList()) + .setReleases(Collections.emptyList()) + .setTopLevelProject("eclipse") + .setSlsaLevel("1") + .build()); - Project p2 = Project - .builder() + this.projects.add(Project.builder() .setName("Prototype thing") .setProjectId("sample.proto") - .setSpecProjectWorkingGroup(Collections.emptyList()) + .setSpecProjectWorkingGroup(Collections.emptyMap()) .setGithubRepos(Arrays.asList(r3)) .setGerritRepos(Arrays.asList(r6)) .setGitlabRepos(Arrays.asList(r8)) .setCommitters(Arrays.asList(u2)) .setProjectLeads(Collections.emptyList()) .setGitlab( - GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()).setProjectGroup("eclipse/dash-second").build()) - .setGithub(GithubProject.builder().setIgnoredRepos(Collections.emptyList()).setOrg("").build()) - .build(); - src.add(p2); + GitlabProject.builder().setIgnoredSubGroups(Collections.emptyList()) + .setProjectGroup("eclipse/dash-second").build()) + .setShortProjectId("sample.proto") + .setWebsiteUrl("someproject.com") + .setSummary("summary") + .setUrl("project.url.com") + .setWebsiteRepo(Collections.emptyList()) + .setLogo("logoUrl.com") + .setTags(Collections.emptyList()) + .setGithub(GithubProject.builder() + .setOrg("") + .setIgnoredRepos(Collections.emptyList()).build()) + .setContributors(Collections.emptyList()) + .setWorkingGroups(Collections.emptyList()) + .setIndustryCollaborations(Collections.emptyList()) + .setReleases(Collections.emptyList()) + .setTopLevelProject("eclipse") + .setSlsaLevel("1") + .build()); - Map<String, String> map = new HashMap<>(); - map.put("id", "proj1"); - Project p3 = Project - .builder() + this.projects.add(Project.builder() .setName("Spec project") .setProjectId("spec.proj") - .setSpecProjectWorkingGroup(map) - .setGithubRepos(Collections.emptyList()) + .setSpecProjectWorkingGroup(Map.of("id", "proj1", "name", "proj1")) + .setGithubRepos(Arrays.asList(r4)) .setGitlabRepos(Arrays.asList(r7)) + .setGerritRepos(Collections.emptyList()) .setGitlab(GitlabProject .builder() .setIgnoredSubGroups(Arrays.asList("eclipse/dash/mirror")) .setProjectGroup("eclipse/dash") .build()) - .setGithub(GithubProject - .builder() - .setIgnoredRepos(Arrays.asList("eclipsefdn-tck/tck-ignored")) - .setOrg("eclipsefdn-tck") - .build()) .setCommitters(Arrays.asList(u1, u2)) .setProjectLeads(Collections.emptyList()) - .build(); - src.add(p3); + .setShortProjectId("spec.proj") + .setSummary("summary") + .setUrl("project.url.com") + .setWebsiteUrl("someproject.com") + .setWebsiteRepo(Collections.emptyList()) + .setLogo("logoUrl.com") + .setTags(Collections.emptyList()) + .setGithub(GithubProject.builder() + .setOrg("eclipsefdn-tck") + .setIgnoredRepos(Arrays.asList("eclipsefdn-tck/tck-ignored")).build()) + .setContributors(Collections.emptyList()) + .setWorkingGroups(Collections.emptyList()) + .setIndustryCollaborations(Collections.emptyList()) + .setReleases(Collections.emptyList()) + .setTopLevelProject("eclipse") + .setSlsaLevel("1") + .build()); } @Override - public Response getProjects(BaseAPIParameters baseParams) { - return Response.ok(baseParams.getPage() == 1 ? new ArrayList<>(src) : Collections.emptyList()).build(); + public Response getProjects(BaseAPIParameters params, int isSpecProject) { + + if (isSpecProject == 1) { + return Response + .ok(projects.stream().filter(p -> p.getSpecWorkingGroup().isPresent()).collect(Collectors.toList())) + .build(); + } + + return Response.ok(projects).build(); } @Override public Response getInterestGroups(BaseAPIParameters params) { - return Response - .ok(Arrays - .asList(InterestGroupData - .builder() - .setProjectId("foundation-internal.ig.mittens") - .setId("1") - .setLogo("") - .setState("active") - .setTitle("Magical IG Tributed To Eclipse News Sources") - .setDescription(Descriptor.builder().setFull("Sample").setSummary("Sample").build()) - .setScope(Descriptor.builder().setFull("Sample").setSummary("Sample").build()) - .setGitlab(GitlabProject - .builder() - .setIgnoredSubGroups(Collections.emptyList()) - .setProjectGroup("eclipse-ig/mittens") - .build()) - .setLeads(Arrays - .asList(User - .builder() - .setUrl("https://api.eclipse.org/account/profile/zacharysabourin") - .setUsername("zacharysabourin") - .build())) - .setParticipants(Arrays - .asList(User - .builder() - .setUrl("https://api.eclipse.org/account/profile/skilpatrick") - .setUsername("skilpatrick") - .build())) - .build())) - .build(); + return Response.ok(Arrays.asList(InterestGroup + .builder() + .setProjectId("foundation-internal.ig.mittens") + .setId("1") + .setLogo("") + .setState("active") + .setTitle("Magical IG Tributed To Eclipse News Sources") + .setDescription(Descriptor.builder().setFull("Sample").setSummary("Sample").build()) + .setScope(Descriptor.builder().setFull("Sample").setSummary("Sample").build()) + .setGitlab(GitlabProject.builder() + .setIgnoredSubGroups(Collections.emptyList()) + .setProjectGroup("eclipse-ig/mittens") + .build()) + .setLeads(Arrays.asList(InterestGroupParticipant + .builder() + .setUrl("https://api.eclipse.org/account/profile/zacharysabourin") + .setUsername("zacharysabourin") + .setFullName("zachary sabourin") + .setOrganization(Organization.builder() + .setDocuments(Collections.emptyMap()) + .setId("id") + .setName("org").build()) + .build())) + .setParticipants(Arrays.asList(InterestGroupParticipant + .builder() + .setUrl("https://api.eclipse.org/account/profile/skilpatrick") + .setUsername("skilpatrick") + .setFullName("Skil Patrick") + .setOrganization(Organization.builder() + .setDocuments(Collections.emptyMap()) + .setId("id") + .setName("org").build()) + .build())) + .setShortProjectId("mittens") + .setResources(Resource.builder().setMembers("members").setWebsite("google.com").build()) + .setMailingList("mailinglist.com") + .build())).build(); } } -- GitLab From f0517bcf80e2225cb5c085c8135a6f89e37f0224 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Fri, 9 Jun 2023 13:38:51 -0400 Subject: [PATCH 3/5] Iss #132 - Replace auth token code with commons version As there is a drupal auth token code that will handle creating auth tokens for non-standard flows, we can now remove the code and use the version in the lib. In the future, some of the user service will be replaced, but most of it can't be yet due to missing fields for Github account lookups, so it will be left alone for stability. --- pom.xml | 14 --- .../git/eca/oauth/EclipseApi.java | 49 ---------- .../git/eca/service/OAuthService.java | 33 ------- .../eca/service/impl/CachedUserService.java | 4 +- .../eca/service/impl/DefaultOAuthService.java | 93 ------------------- ...AuthService.java => MockTokenService.java} | 4 +- 6 files changed, 4 insertions(+), 193 deletions(-) delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java rename src/test/java/org/eclipsefoundation/git/eca/test/service/impl/{MockOAuthService.java => MockTokenService.java} (83%) diff --git a/pom.xml b/pom.xml index e5500bc2..0905d4ab 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,6 @@ <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> <sonar.coverage.jacoco.xmlReportPaths>${project.basedir}/target/jacoco-report/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths> <sonar.junit.reportPath>${project.build.directory}/surefire-reports</sonar.junit.reportPath> - <sonar.host.url>https://sonarcloud.io</sonar.host.url> <sonar.organization>eclipse-foundation-it</sonar.organization> <sonar.projectKey>eclipse-foundation-it_git-eca-rest-api</sonar.projectKey> <sonar.projectName>Git ECA REST API</sonar.projectName> @@ -92,7 +91,6 @@ <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> - <version>1.70</version> </dependency> <dependency> @@ -134,18 +132,6 @@ <scope>provided</scope> </dependency> - <!-- Third-party reqs --> - <dependency> - <groupId>com.github.scribejava</groupId> - <artifactId>scribejava-apis</artifactId> - <version>6.4.1</version> - </dependency> - <!-- Caching --> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - </dependency> - <!-- Test requirements --> <dependency> <groupId>io.quarkus</groupId> diff --git a/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java b/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java deleted file mode 100644 index b89a3703..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java +++ /dev/null @@ -1,49 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.oauth; - -import com.github.scribejava.core.builder.api.DefaultApi20; -import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication; -import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme; - -/** - * Wrapper around the OAuth API for Scribejava. Enables OAuth2.0 binding to the - * Eclipse Foundation OAuth server. - * - * @author Martin Lowe - * - */ -public class EclipseApi extends DefaultApi20 { - - @Override - public String getAccessTokenEndpoint() { - return "https://accounts.eclipse.org/oauth2/token"; - } - - @Override - protected String getAuthorizationBaseUrl() { - return null; - } - - @Override - public ClientAuthentication getClientAuthentication() { - return RequestBodyAuthenticationScheme.instance(); - } - - private static class InstanceHolder { - private static final EclipseApi INSTANCE = new EclipseApi(); - } - - public static EclipseApi instance() { - return InstanceHolder.INSTANCE; - } -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java b/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java deleted file mode 100644 index 144601ae..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java +++ /dev/null @@ -1,33 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.service; - -/** - * Used to generate OAuth tokens for use with internal services rather than - * bolted on introspection. This is required over the (now deprecated) Elytron - * plugin or the OIDC plugin as those plugins work with requests to validate - * incoming rather than outgoing requests. - * - * @author Martin Lowe - * - */ -public interface OAuthService { - - /** - * Retrieve an access token for the service from the Eclipse API for internal - * usage. - * - * @return current access token, or null if none could be retrieved for current - * API credentials/settings. - */ - String getToken(); -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java index 1d576e0d..de6836a8 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java @@ -29,10 +29,10 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.efservices.api.models.Project; +import org.eclipsefoundation.efservices.services.DrupalTokenService; import org.eclipsefoundation.git.eca.api.AccountsAPI; import org.eclipsefoundation.git.eca.api.BotsAPI; import org.eclipsefoundation.git.eca.api.models.EclipseUser; -import org.eclipsefoundation.git.eca.service.OAuthService; import org.eclipsefoundation.git.eca.service.UserService; import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.slf4j.Logger; @@ -63,7 +63,7 @@ public class CachedUserService implements UserService { BotsAPI bots; @Inject - OAuthService oauth; + DrupalTokenService oauth; @Inject CachingService cache; diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java deleted file mode 100644 index 713b0988..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java +++ /dev/null @@ -1,93 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.service.impl; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import javax.annotation.PostConstruct; -import javax.enterprise.context.ApplicationScoped; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipsefoundation.git.eca.oauth.EclipseApi; -import org.eclipsefoundation.git.eca.service.OAuthService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.github.scribejava.core.builder.ServiceBuilder; -import com.github.scribejava.core.model.OAuth2AccessToken; -import com.github.scribejava.core.oauth.OAuth20Service; - -/** - * Default implementation for requesting an OAuth request token. The reason that - * this class is implemented over the other implementations baked into Quarkus - * is to better bind to the Drupal OAuth APIs. - * - * @author Martin Lowe - * - */ -@ApplicationScoped -public class DefaultOAuthService implements OAuthService { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOAuthService.class); - - @ConfigProperty(name = "oauth2.client-id") - String id; - @ConfigProperty(name = "oauth2.client-secret") - String secret; - @ConfigProperty(name = "oauth2.scope") - String scope; - - // service reference (as we only need one) - private OAuth20Service service; - - // token state vars - private long expirationTime; - private String accessToken; - - /** - * Create an OAuth service reference. - */ - @PostConstruct - void createServiceRef() { - this.service = new ServiceBuilder(id).apiSecret(secret).scope(scope).build(EclipseApi.instance()); - } - - @Override - public String getToken() { - // lock on the class instance to stop multiple threads from requesting new - // tokens at the same time - synchronized (this) { - if (accessToken == null || System.currentTimeMillis() >= expirationTime) { - // clear access token - this.accessToken = null; - try { - OAuth2AccessToken requestToken = service.getAccessTokenClientCredentialsGrant(); - if (requestToken != null) { - this.accessToken = requestToken.getAccessToken(); - this.expirationTime = System.currentTimeMillis() - + TimeUnit.SECONDS.toMillis(requestToken.getExpiresIn().longValue()); - } - } catch (IOException e) { - LOGGER.error("Issue communicating with OAuth server for authentication", e); - } catch (InterruptedException e) { - LOGGER.error("Authentication communication was interrupted before completion", e); - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { - LOGGER.error("Error while retrieving access token for request", e); - } - } - } - return accessToken; - } - -} diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java b/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockTokenService.java similarity index 83% rename from src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java rename to src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockTokenService.java index 8ce32a29..4b8bc5ce 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java +++ b/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockTokenService.java @@ -13,13 +13,13 @@ package org.eclipsefoundation.git.eca.test.service.impl; import javax.inject.Singleton; -import org.eclipsefoundation.git.eca.service.OAuthService; +import org.eclipsefoundation.efservices.services.DrupalTokenService; import io.quarkus.test.Mock; @Mock @Singleton -public class MockOAuthService implements OAuthService { +public class MockTokenService implements DrupalTokenService { @Override public String getToken() { -- GitLab From 7b70fa6f444467cf2e2a079639ce3ccc4f0c5124 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Fri, 9 Jun 2023 13:52:24 -0400 Subject: [PATCH 4/5] Fix projects precache timing out before load is finished --- src/main/resources/application.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 33b399d0..34c35e6a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,7 @@ quarkus.rest-client."org.eclipsefoundation.git.eca.api.AccountsAPI".scope=javax.enterprise.context.ApplicationScoped quarkus.rest-client."org.eclipsefoundation.git.eca.api.AccountsAPI".url=https://api.eclipse.org -org.eclipsefoundation.git.eca.api.ProjectsAPI/mp-rest/url=https://projects.eclipse.org +projects-api/mp-rest/url=https://projects.eclipse.org +eclipse.cache.loading."projects".timeout=10 org.eclipsefoundation.git.eca.api.BotsAPI/mp-rest/url=https://api.eclipse.org quarkus.rest-client."org.eclipsefoundation.git.eca.api.GitlabAPI".url=https://gitlab.eclipse.org/api/v4/ -- GitLab From 50711952b3de8b2d2d1ddd8a6dceb35cf50b8240 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Fri, 9 Jun 2023 14:18:08 -0400 Subject: [PATCH 5/5] Add start at boot for projects loading --- src/main/resources/application.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 34c35e6a..09df93d1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,6 +2,7 @@ quarkus.rest-client."org.eclipsefoundation.git.eca.api.AccountsAPI".scope=javax. quarkus.rest-client."org.eclipsefoundation.git.eca.api.AccountsAPI".url=https://api.eclipse.org projects-api/mp-rest/url=https://projects.eclipse.org eclipse.cache.loading."projects".timeout=10 +eclipse.cache.loading."projects".start-at-boot=true org.eclipsefoundation.git.eca.api.BotsAPI/mp-rest/url=https://api.eclipse.org quarkus.rest-client."org.eclipsefoundation.git.eca.api.GitlabAPI".url=https://gitlab.eclipse.org/api/v4/ -- GitLab