diff --git a/.gitignore b/.gitignore index 51fe9e44da7aeec20cfdc695b9fab2a8e527d82f..3b2dca2c06ce55d0d6eb697c54343365960639f7 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ release.properties secret/ secrets/ secret.properties +*.pem # Additional build resources src/test/resources/schemas diff --git a/pom.xml b/pom.xml index 216db0fa79705bb98311e31e0d37c04c86cc0cf8..3baf3ec64be211e7a3a15756b679c1036a30b24e 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,21 @@ <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-context-propagation</artifactId> </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-smallrye-jwt</artifactId> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + </dependency> + <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on --> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15on</artifactId> + <version>1.70</version> + </dependency> + <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-rest-client</artifactId> diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..d68e938cc0e4b6a783ccaaa86fc9a3da8b37c7bf --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java @@ -0,0 +1,50 @@ +/** + * 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.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipsefoundation.git.eca.api.models.GithubAccessToken; +import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest; + +/** + * Bindings used by the webhook logic to retrieve data about the PR to be validated. + * + * @author Martin Lowe + * + */ +@RegisterRestClient(baseUri = "https://api.github.com") +@Produces("application/json") +public interface GithubAPI { + + @GET + @Path("repos/{repoFull}/pulls/{pullNumber}/commits") + public Response getCommits(@HeaderParam("authorization") String bearer, @HeaderParam("X-GitHub-Api-Version") String apiVersion, + @PathParam("repoFull") String repoFull, @PathParam("pullNumber") int pullNumber); + + @POST + @Path("repos/{repoFull}/statuses/{prHeadSha}") + public Response updateStatus(@HeaderParam("authorization") String bearer, @HeaderParam("X-GitHub-Api-Version") String apiVersion, + @PathParam("repoFull") String repoFull, @PathParam("prHeadSha") String prHeadSha, GithubCommitStatusRequest commitStatusUpdate); + + @POST + @Path("app/installations/{installationId}/access_tokens") + public GithubAccessToken getNewAccessToken(@HeaderParam("authorization") String bearer, + @HeaderParam("X-GitHub-Api-Version") String apiVersion, @PathParam("installationId") String installationId); +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubAccessToken.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubAccessToken.java new file mode 100644 index 0000000000000000000000000000000000000000..437e4a6c19466e7864c22203a7a6db409c4602e1 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubAccessToken.java @@ -0,0 +1,43 @@ +/** + * 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.models; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +/** + * @author martin + * + */ +@AutoValue +@JsonDeserialize(builder = AutoValue_GithubAccessToken.Builder.class) +public abstract class GithubAccessToken { + + public abstract String getToken(); + public abstract LocalDateTime getExpiresAt(); + + public static Builder builder() { + return new AutoValue_GithubAccessToken.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setToken(String token); + public abstract Builder setExpiresAt(LocalDateTime expiresAt); + + public abstract GithubAccessToken build(); + } +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommit.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommit.java new file mode 100644 index 0000000000000000000000000000000000000000..811c9d14da69f7d9cdbaba36e45fa16218d964d5 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommit.java @@ -0,0 +1,79 @@ +/** + * 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.models; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +/** + * @author Martin Lowe + * + */ +@AutoValue +@JsonDeserialize(builder = AutoValue_GithubCommit.Builder.class) +public abstract class GithubCommit { + public abstract String getSha(); + public abstract CommitData getCommit(); + + public static Builder builder() { + return new AutoValue_GithubCommit.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setSha(String sha); + public abstract Builder setCommit(CommitData commit); + + public abstract GithubCommit build(); + } + + @AutoValue + @JsonDeserialize(builder = AutoValue_GithubCommit_CommitData.Builder.class) + public abstract static class CommitData { + public abstract CommitUser getAuthor(); + public abstract CommitUser getCommitter(); + + public static Builder builder() { + return new AutoValue_GithubCommit_CommitData.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setAuthor(CommitUser author); + public abstract Builder setCommitter(CommitUser committer); + + public abstract CommitData build(); + } + } + @AutoValue + @JsonDeserialize(builder = AutoValue_GithubCommit_CommitUser.Builder.class) + public abstract static class CommitUser { + public abstract String getName(); + public abstract String getEmail(); + + public static Builder builder() { + return new AutoValue_GithubCommit_CommitUser.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setName(String name); + public abstract Builder setEmail(String email); + + public abstract CommitUser build(); + } + } +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommitStatusRequest.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommitStatusRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..53522c1f9d33c8be02ac06d7fb938eeeefe02b2f --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubCommitStatusRequest.java @@ -0,0 +1,44 @@ +/** + * 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.models; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +/** + * @author martin + * + */ +@AutoValue +@JsonDeserialize(builder = AutoValue_GithubCommitStatusRequest.Builder.class) +public abstract class GithubCommitStatusRequest { + public abstract String getState(); + public abstract String getTargetUrl(); + public abstract String getDescription(); + public abstract String getContext(); + + public static Builder builder() { + return new AutoValue_GithubCommitStatusRequest.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setState(String state); + public abstract Builder setTargetUrl(String targetUrl); + public abstract Builder setDescription(String description); + public abstract Builder setContext(String context); + + public abstract GithubCommitStatusRequest build(); + } +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..7c0e93ac18311c64aae0b083257276b75d13cc5d --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubWebhookRequest.java @@ -0,0 +1,134 @@ +/** + * 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.models; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +/** + * Represents incoming webhook requests from the Git ECA app installations on Github. + * + * @author Martin Lowe + * + */ +@AutoValue +@JsonDeserialize(builder = AutoValue_GithubWebhookRequest.Builder.class) +public abstract class GithubWebhookRequest { + + public abstract String getAction(); + + public abstract Installation getInstallation(); + + public abstract Repository getRepository(); + + public abstract PullRequest getPullRequest(); + + public static Builder builder() { + return new AutoValue_GithubWebhookRequest.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setAction(String action); + + public abstract Builder setInstallation(Installation installation); + + public abstract Builder setRepository(Repository repository); + + public abstract Builder setPullRequest(PullRequest pullRequest); + + public abstract GithubWebhookRequest build(); + } + + @AutoValue + @JsonDeserialize(builder = AutoValue_GithubWebhookRequest_Repository.Builder.class) + public abstract static class Repository { + + public abstract String getFullName(); + + public abstract String getHtmlUrl(); + + public static Builder builder() { + return new AutoValue_GithubWebhookRequest_Repository.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setFullName(String fullName); + + public abstract Builder setHtmlUrl(String htmlUrl); + + public abstract Repository build(); + } + } + + @AutoValue + @JsonDeserialize(builder = AutoValue_GithubWebhookRequest_PullRequest.Builder.class) + public abstract static class PullRequest { + + public abstract Integer getNumber(); + public abstract PullRequestHead getHead(); + + public static Builder builder() { + return new AutoValue_GithubWebhookRequest_PullRequest.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setNumber(Integer number); + public abstract Builder setHead(PullRequestHead head); + + public abstract PullRequest build(); + } + } + + @AutoValue + @JsonDeserialize(builder = AutoValue_GithubWebhookRequest_PullRequestHead.Builder.class) + public abstract static class PullRequestHead { + + public abstract String getSha(); + + public static Builder builder() { + return new AutoValue_GithubWebhookRequest_PullRequestHead.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setSha(String sha); + + public abstract PullRequestHead build(); + } + } + + @AutoValue + @JsonDeserialize(builder = AutoValue_GithubWebhookRequest_Installation.Builder.class) + public abstract static class Installation { + public abstract String getId(); + + public static Builder builder() { + return new AutoValue_GithubWebhookRequest_Installation.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setId(String id); + + public abstract Installation build(); + } + } +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/InterestGroupData.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java similarity index 92% rename from src/main/java/org/eclipsefoundation/git/eca/model/InterestGroupData.java rename to src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java index e2fd06bd8cf50dc3194849403206a0b00885cfb2..bea777d515c2554cae4c922221e9bd3c8910a5d8 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/InterestGroupData.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/InterestGroupData.java @@ -1,9 +1,9 @@ -package org.eclipsefoundation.git.eca.model; +package org.eclipsefoundation.git.eca.api.models; import java.util.List; -import org.eclipsefoundation.git.eca.model.Project.GitlabProject; -import org.eclipsefoundation.git.eca.model.Project.User; +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; diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/Project.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java similarity index 99% rename from src/main/java/org/eclipsefoundation/git/eca/model/Project.java rename to src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java index ba5be582840bfc05fcd2fd1db3f8dddcb083b7dd..fa07ce5d1a85dfe6aa2814978606a02ca2dd1e13 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/Project.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/Project.java @@ -9,7 +9,7 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -package org.eclipsefoundation.git.eca.model; +package org.eclipsefoundation.git.eca.api.models; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/SystemHook.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/SystemHook.java similarity index 98% rename from src/main/java/org/eclipsefoundation/git/eca/model/SystemHook.java rename to src/main/java/org/eclipsefoundation/git/eca/api/models/SystemHook.java index b352d59bc7dd24e991d62030c2f22d82bee6ff19..ddc7fd66b391baba7b8639024ea5ca8ea537d289 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/SystemHook.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/SystemHook.java @@ -9,7 +9,7 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -package org.eclipsefoundation.git.eca.model; +package org.eclipsefoundation.git.eca.api.models; import java.time.ZonedDateTime; import java.util.List; diff --git a/src/main/java/org/eclipsefoundation/git/eca/config/BCSecurityProvider.java b/src/main/java/org/eclipsefoundation/git/eca/config/BCSecurityProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..ee86744b73a93f6bb175f8dfdb5ddb8353ebfa05 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/config/BCSecurityProvider.java @@ -0,0 +1,37 @@ +/** + * 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.config; + +import java.security.Security; + +import javax.annotation.PostConstruct; +import javax.inject.Singleton; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import io.quarkus.runtime.Startup; + +/** + * Adds Bouncycastle (BC) as a security provider to enable PKCS1 consumption. + * + * @author Martin Lowe + * + */ +@Startup +@Singleton +public class BCSecurityProvider { + + @PostConstruct + public void init() { + Security.addProvider(new BouncyCastleProvider()); + } +} 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 8831591feba1faa8a87f8765b652d802da9e856a..0650a2e89a34d6c0c53f4fea19defbbac3c79a8f 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java +++ b/src/main/java/org/eclipsefoundation/git/eca/helper/CommitHelper.java @@ -16,8 +16,8 @@ import java.util.stream.Collectors; import javax.ws.rs.core.MultivaluedMap; +import org.eclipsefoundation.git.eca.api.models.Project; import org.eclipsefoundation.git.eca.model.Commit; -import org.eclipsefoundation.git.eca.model.Project; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames; import org.jboss.resteasy.specimpl.MultivaluedMapImpl; diff --git a/src/main/java/org/eclipsefoundation/git/eca/helper/JwtHelper.java b/src/main/java/org/eclipsefoundation/git/eca/helper/JwtHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..4a737d7394d9e85d324c247aaf6d935e4e17db05 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/helper/JwtHelper.java @@ -0,0 +1,58 @@ +/** + * 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.helper; + +import java.io.FileReader; +import java.nio.file.Paths; +import java.security.PrivateKey; + +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helper to load external resources as a JWT key. + * + * @author Martin Lowe + * + */ +public class JwtHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(JwtHelper.class); + + /** + * Reads in external PEM keys in a way that supports both PKCS#1 and PKCS#8. This is needed as the GH-provided RSA key + * is encoded using PKCS#1 and is not available for consumption in OOTB Java/smallrye, and smallrye's default PEM reader + * only reads within the resources path, so external file support isn't really available. + * + * Src: https://stackoverflow.com/questions/41934846/read-rsa-private-key-of-format-pkcs1-in-java + * + * @param location the location of the file + * @return the PrivateKey instance for the PEM file at the location, or null if it could not be read/parsed. + */ + public static PrivateKey getExternalPrivateKey(String location) { + // create auto-closing reading resources for the external PEM file + try (FileReader keyReader = new FileReader(Paths.get(location).toFile()); PEMParser pemParser = new PEMParser(keyReader)) { + // use the BouncyCastle provider for PKCS#1 support (not available ootb) + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + // create the key and retrieve the PrivateKey portion + return converter.getKeyPair((PEMKeyPair) pemParser.readObject()).getPrivate(); + } catch (Exception e) { + LOGGER.error("Error while loading private pem", e); + } + return null; + } + + private JwtHelper() { + } +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/PrivateProjectData.java b/src/main/java/org/eclipsefoundation/git/eca/model/PrivateProjectData.java index 2a4215a4b50b7c3e7831feb57775a38ea2defd17..b46f7b58984fea61a0b4a90b94cdab1b70f0151d 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/PrivateProjectData.java +++ b/src/main/java/org/eclipsefoundation/git/eca/model/PrivateProjectData.java @@ -20,6 +20,12 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.google.auto.value.AutoValue; +/** + * Model for Gitlab private project report logs. Used to report on historic projects in the EF hosted Gitlab instance. + * + * @author Zachary Sabourin + * + */ @AutoValue @JsonDeserialize(builder = AutoValue_PrivateProjectData.Builder.class) public abstract class PrivateProjectData { diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/mappers/BaseEntityMapper.java b/src/main/java/org/eclipsefoundation/git/eca/model/mappers/BaseEntityMapper.java index 145e760742969e001c395d3b575d5f8f6c1a0b4d..0961f0faf560b7b19d625fa1bca824e7d045dd61 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/mappers/BaseEntityMapper.java +++ b/src/main/java/org/eclipsefoundation/git/eca/model/mappers/BaseEntityMapper.java @@ -15,7 +15,6 @@ import org.eclipsefoundation.persistence.dao.PersistenceDao; import org.eclipsefoundation.persistence.dto.BareNode; import org.mapstruct.Context; import org.mapstruct.InheritInverseConfiguration; -import org.mapstruct.MapperConfig; import org.mapstruct.ObjectFactory; import org.mapstruct.TargetType; diff --git a/src/main/java/org/eclipsefoundation/git/eca/namespace/GithubCommitStatuses.java b/src/main/java/org/eclipsefoundation/git/eca/namespace/GithubCommitStatuses.java new file mode 100644 index 0000000000000000000000000000000000000000..2017fca8988bfccb10fee240da2d03e53f3800c4 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/namespace/GithubCommitStatuses.java @@ -0,0 +1,52 @@ +/** + * 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.namespace; + +/** + * Statuses used for Github commit status objects. Contains basic messaging used in the description of the status to be + * sent back to Github. + * + * @author Martin Lowe + * + */ +public enum GithubCommitStatuses { + + SUCCESS("The author(s) of the pull request is covered by necessary legal agreements in order to proceed!"), + FAILURE("An unexpected error has occurred. Please contact webmaster@eclipse.org."), + ERROR("The author(s) of the pull request is not covered by necessary legal agreements in order to proceed."), + PENDING("Eclipse Foundation Contributor Agreement validation is in progress."); + + private String message; + + /** + * Instantiate the enum status with the display message. + * + * @param message the display message associated with the status. + */ + private GithubCommitStatuses(String message) { + this.message = message; + } + + /** + * Returns the message associated with the status. + * + * @return the string message for status. + */ + public String getMessage() { + return this.message; + } + + @Override + public String toString() { + return this.name().toLowerCase(); + } +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java new file mode 100644 index 0000000000000000000000000000000000000000..26685b2d59badef91c41f62935599172820a5d08 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java @@ -0,0 +1,211 @@ +/** + * 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.resource; + +import java.net.URI; +import java.util.List; +import java.util.stream.Collectors; + +import javax.inject.Inject; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.core.model.RequestWrapper; +import org.eclipsefoundation.core.service.APIMiddleware; +import org.eclipsefoundation.git.eca.api.GithubAPI; +import org.eclipsefoundation.git.eca.api.models.GithubAccessToken; +import org.eclipsefoundation.git.eca.api.models.GithubCommit; +import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest; +import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest; +import org.eclipsefoundation.git.eca.helper.JwtHelper; +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.GithubCommitStatuses; +import org.eclipsefoundation.git.eca.namespace.ProviderType; +import org.eclipsefoundation.git.eca.service.ValidationService; +import org.jboss.resteasy.annotations.jaxrs.HeaderParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.quarkus.cache.CacheResult; +import io.smallrye.jwt.auth.principal.JWTParser; +import io.smallrye.jwt.build.Jwt; + +/** + * Resource for processing Github pull request events, used to update commit status entries for the Git ECA application. + * + * @author Martin Lowe + * + */ +@Path("webhooks/github") +public class GithubWebhooksResource { + private static final Logger LOGGER = LoggerFactory.getLogger(GithubWebhooksResource.class); + + @ConfigProperty(name = "smallrye.jwt.sign.key.location") + String location; + @ConfigProperty(name = "eclipse.webhooks.github.context") + String context; + @ConfigProperty(name = "eclipse.webhooks.github.server-target") + String serverTarget; + + @ConfigProperty(name = "eclipse.github.default-api-version", defaultValue = "2022-11-28") + String apiVersion; + + @Inject + RequestWrapper wrapper; + @Inject + APIMiddleware middleware; + @Inject + ValidationService validationService; + + @Inject + JWTParser parser; + + @RestClient + GithubAPI ghApi; + + /** + * Entry point for processing Github webhook requests. Accepts standard fields as described in the <a href= + * "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#webhook-payload-object-common-properties">Webhook + * properties documentation</a>, as well as the <a href= + * "https://docs.github.com/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request">Pull + * Request event type documentation.</a> + * + * @param deliveryId Github provided unique delivery ID + * @param eventType the type of webhook event that was fired + * @param request the webhook request body. + * @return an OK status when done processing. + */ + @POST + @Path("check") + public Response processGithubCheck(@HeaderParam("X-GitHub-Delivery") String deliveryId, @HeaderParam("X-GitHub-Event") String eventType, + GithubWebhookRequest request) { + // If event isn't a pr event, drop as we don't process them + if (!"pull_request".equalsIgnoreCase(eventType)) { + return Response.ok().build(); + } + LOGGER.trace("Processing PR event for install {} with delivery ID of {}", request.getInstallation().getId(), deliveryId); + // start validation process + ValidationRequest vr = generateRequest(request); + String fingerprint = validationService.generateRequestHash(vr); + updateCommitStatus(request, GithubCommitStatuses.PENDING, fingerprint); + + // validate the response + ValidationResponse r = validationService.validateIncomingRequest(vr, wrapper); + if (r.getPassed()) { + updateCommitStatus(request, GithubCommitStatuses.SUCCESS, fingerprint); + } else { + updateCommitStatus(request, GithubCommitStatuses.FAILURE, fingerprint); + } + return Response.ok().build(); + } + + /** + * Sends off a POST to update the commit status given a context for the current PR. + * + * @param request the current webhook update request + * @param state the state to set the status to + * @param fingerprint the internal unique string for the set of commits being processed + */ + private void updateCommitStatus(GithubWebhookRequest request, GithubCommitStatuses state, String fingerprint) { + LOGGER + .trace("Generated access token for installation {}: {}", request.getInstallation().getId(), + getAccessToken(request.getInstallation().getId()).getToken()); + ghApi + .updateStatus(getBearerString(request.getInstallation().getId()), apiVersion, request.getRepository().getFullName(), + request.getPullRequest().getHead().getSha(), + GithubCommitStatusRequest + .builder() + .setDescription(state.getMessage()) + .setState(state.toString()) + .setTargetUrl(serverTarget + "/git/eca/status/" + fingerprint + "/ui") + .setContext(context) + .build()); + } + + /** + * Generate the validation request for the current GH Webhook request. + * + * @param request the current webhook request for validation + * @return the Validation Request body to be used in validating data + */ + private ValidationRequest generateRequest(GithubWebhookRequest request) { + // get the commits that will be validated, don't cache as changes can come in too fast for it to be useful + List<GithubCommit> commits = middleware + .getAll(i -> ghApi + .getCommits(getBearerString(request.getInstallation().getId()), apiVersion, request.getRepository().getFullName(), + request.getPullRequest().getNumber()), + GithubCommit.class); + LOGGER + .trace("Retrieved {} commits for PR {} in repo {}", commits.size(), request.getPullRequest().getNumber(), + request.getRepository().getHtmlUrl()); + // set up the validation request from current data + return ValidationRequest + .builder() + .setProvider(ProviderType.GITHUB) + .setRepoUrl(URI.create(request.getRepository().getHtmlUrl())) + .setCommits(commits + .stream() + .map(c -> Commit + .builder() + .setHash(c.getSha()) + .setAuthor(GitUser + .builder() + .setMail(c.getCommit().getAuthor().getEmail()) + .setName(c.getCommit().getAuthor().getName()) + .build()) + .setCommitter(GitUser + .builder() + .setMail(c.getCommit().getCommitter().getEmail()) + .setName(c.getCommit().getCommitter().getName()) + .build()) + .build()) + .collect(Collectors.toList())) + .build(); + } + + /** + * Retrieve the bearer token string for the current installation. + * + * @param installationId the installation to generate a bearer string for + * @return the bearer string for the current request + */ + protected String getBearerString(String installationId) { + return "Bearer " + getAccessToken(installationId).getToken(); + } + + /** + * Retrieve a new cached Github access token, using a JWT generated from the GH provided PKCS#1 private key. + * + * @param installationId the installation that the access token is being generated for + * @return the access token to be used in the requests. + */ + @CacheResult(cacheName = "accesstoken") + protected GithubAccessToken getAccessToken(String installationId) { + return ghApi.getNewAccessToken("Bearer " + generateJwt(), apiVersion, installationId); + } + + /** + * Generate the JWT to use for Github requests. This is stored in a special one-shot cache method secured to this class, + * and not to be used for other requests that require JWTs. + * + * @return signed JWT using the issuer and secret defined in the secret properties. + */ + private String generateJwt() { + return Jwt.subject("EclipseWebmaster").sign(JwtHelper.getExternalPrivateKey(location)); + } +} 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 6345a0f67b373a4d25a2ebc35a95e6fa7a5d0290..7985ad0a78d5c826edf8ec3dc8daf40571dc90b6 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java @@ -31,8 +31,8 @@ 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.api.models.Project; import org.eclipsefoundation.git.eca.dto.CommitValidationStatus; -import org.eclipsefoundation.git.eca.model.Project; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.model.ValidationResponse; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; @@ -48,9 +48,8 @@ import io.quarkus.qute.Location; import io.quarkus.qute.Template; /** - * ECA validation endpoint for Git commits. Will use information from the bots, - * projects, and accounts API to validate commits passed to this endpoint. - * Should be as system agnostic as possible to allow for any service to request + * ECA validation endpoint for Git commits. Will use information from the bots, projects, and accounts API to validate + * commits passed to this endpoint. Should be as system agnostic as possible to allow for any service to request * validation with less reliance on services external to the Eclipse foundation. * * @author Martin Lowe, Zachary Sabourin @@ -88,16 +87,14 @@ public class ValidationResource { Template membershipTemplateWeb; /** - * Consuming a JSON request, this method will validate all passed commits, using - * the repo URL and the repository provider. These commits will be validated to - * ensure that all users are covered either by an ECA, or are committers on the - * project. In the case of ECA-only contributors, an additional sign off footer - * is required in the body of the commit. + * Consuming a JSON request, this method will validate all passed commits, using the repo URL and the repository + * provider. These commits will be validated to ensure that all users are covered either by an ECA, or are committers on + * the project. In the case of ECA-only contributors, an additional sign off footer is required in the body of the + * commit. * * @param req the request containing basic data plus the commits to be validated - * @return a web response indicating success or failure for each commit, along - * with standard messages that may be - * used to give users context on failure. + * @return a web response indicating success or failure for each commit, along with standard messages that may be used + * to give users context on failure. * @throws MalformedURLException */ @POST @@ -110,7 +107,7 @@ public class ValidationResource { } else { // create a stubbed response with the errors ValidationResponse out = ValidationResponse.builder().build(); - messages.forEach(m -> out.addError(m, null,APIStatusCode.ERROR_DEFAULT)); + messages.forEach(m -> out.addError(m, null, APIStatusCode.ERROR_DEFAULT)); return out.toResponse(); } } @@ -129,18 +126,19 @@ public class ValidationResource { if (statuses.isEmpty()) { return Response.status(404).build(); } - List<Project> ps = projects.retrieveProjectsForRepoURL(statuses.get(0).getRepoUrl(), - statuses.get(0).getProvider()); - return Response.ok().entity(membershipTemplateWeb.data("statuses", statuses, "repoUrl", - statuses.get(0).getRepoUrl(), "project", ps.isEmpty() ? null : ps.get(0)).render()).build(); + List<Project> ps = projects.retrieveProjectsForRepoURL(statuses.get(0).getRepoUrl(), statuses.get(0).getProvider()); + return Response + .ok() + .entity(membershipTemplateWeb + .data("statuses", statuses, "repoUrl", statuses.get(0).getRepoUrl(), "project", ps.isEmpty() ? null : ps.get(0)) + .render()) + .build(); } @GET @Path("/lookup") public Response getUserStatus(@QueryParam("email") String email) { - EclipseUser user = users.getUser(email); - if (Objects.isNull(user)) { return Response.status(404).build(); } @@ -148,17 +146,15 @@ public class ValidationResource { if (!user.getECA().getSigned()) { return Response.status(403).build(); } - return Response.ok().build(); } /** - * Check if there are any issues with the validation request, returning error - * messages if there are issues with the request. + * Check if there are any issues with the validation request, returning error messages if there are issues with the + * request. * * @param req the current validation request - * @return a list of error messages to report, or an empty list if there are no - * errors with the request. + * @return a list of error messages to report, or an empty list if there are no errors with the request. */ private List<String> checkRequest(ValidationRequest req) { // check that we have commits to validate diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/WebhooksResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/WebhooksResource.java index c94fc8bbcead93af5ab688faad8cd08337c3f5c0..a9fd5b266d9eae8d1544371f00cef23fa701fcc7 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/WebhooksResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/WebhooksResource.java @@ -17,7 +17,7 @@ import javax.ws.rs.Path; import javax.ws.rs.core.Response; import org.eclipsefoundation.core.model.RequestWrapper; -import org.eclipsefoundation.git.eca.model.SystemHook; +import org.eclipsefoundation.git.eca.api.models.SystemHook; import org.eclipsefoundation.git.eca.namespace.EventType; import org.eclipsefoundation.git.eca.service.SystemHookService; import org.jboss.resteasy.annotations.jaxrs.HeaderParam; @@ -27,14 +27,12 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -@Path("/webhooks") +@Path("webhooks/gitlab") public class WebhooksResource { - private static final Logger LOGGER = LoggerFactory.getLogger(WebhooksResource.class); @Inject RequestWrapper wrapper; - @Inject SystemHookService hookService; @@ -42,30 +40,26 @@ public class WebhooksResource { ObjectMapper om; @POST - @Path("/gitlab/system") + @Path("system") public Response processGitlabHook(@HeaderParam("X-Gitlab-Event") String eventHeader, String jsonBody) { - // Do not process if header is incorrect - if (!hasValidHeader(eventHeader)) { + if (!"system hook".equalsIgnoreCase(eventHeader)) { return Response.ok().build(); } + // based on event name in body, process the event hook String eventName = readEventName(jsonBody); EventType type = EventType.getType(eventName); - switch (type) { case PROJECT_CREATE: hookService.processProjectCreateHook(wrapper, convertRequestToHook(jsonBody)); break; - case PROJECT_DESTROY: hookService.processProjectDeleteHook(wrapper, convertRequestToHook(jsonBody)); break; - case PROJECT_RENAME: hookService.processProjectRenameHook(wrapper, convertRequestToHook(jsonBody)); break; - case UNSUPPORTED: default: LOGGER.trace("Dropped event: {}", eventName); @@ -76,8 +70,8 @@ public class WebhooksResource { } /** - * Processes the json body contents and converts them to a SystemHook object. - * Returns null if the json body couldn't be processed. + * Processes the json body contents and converts them to a SystemHook object. Returns null if the json body couldn't be + * processed. * * @param jsonBody the json body as a string * @return A SystemHook object converted from a json string @@ -92,9 +86,8 @@ public class WebhooksResource { } /** - * Processes the json body contents and returns the event_name field. Returns - * null if the was a processing error or if the event_name field is - * null/missing. + * Processes the json body contents and returns the event_name field. Returns null if the was a processing error or if + * the event_name field is null/missing. * * @param jsonBody the json body as a String * @return the event_name field @@ -107,14 +100,4 @@ public class WebhooksResource { return null; } } - - /** - * Validates that the hook header contains the required value - * - * @param eventHeader the event header value - * @return true if valid, false if not - */ - private boolean hasValidHeader(String eventHeader) { - return eventHeader != null && eventHeader.equalsIgnoreCase("system hook"); - } } diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java b/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java index 9efdc637487785aa1e7d9f9a61d97ce25f978076..8c2d182b10bf40196da01e8d4ce116339e34d6da 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/InterestGroupService.java @@ -2,8 +2,8 @@ package org.eclipsefoundation.git.eca.service; import java.util.List; -import org.eclipsefoundation.git.eca.model.InterestGroupData; -import org.eclipsefoundation.git.eca.model.Project; +import org.eclipsefoundation.git.eca.api.models.InterestGroupData; +import org.eclipsefoundation.git.eca.api.models.Project; /** * Service for retrieving and interacting with interest groups. diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java b/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java index 925ddfda35a9dee3a55b5052cda6cd19b6d7ed92..54c2199f477ffbe7b149a485617aaf04ad108b74 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/ProjectsService.java @@ -13,7 +13,7 @@ package org.eclipsefoundation.git.eca.service; import java.util.List; -import org.eclipsefoundation.git.eca.model.Project; +import org.eclipsefoundation.git.eca.api.models.Project; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.namespace.ProviderType; diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/SystemHookService.java b/src/main/java/org/eclipsefoundation/git/eca/service/SystemHookService.java index 2ec8bdc54510a482b4c0794b79cc820f6c730b58..3ddab46e18020a1add565b1d7a2f3be9e2cf63ec 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/SystemHookService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/SystemHookService.java @@ -12,7 +12,7 @@ package org.eclipsefoundation.git.eca.service; import org.eclipsefoundation.core.model.RequestWrapper; -import org.eclipsefoundation.git.eca.model.SystemHook; +import org.eclipsefoundation.git.eca.api.models.SystemHook; /** * Processes the various system hooks received. 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 1c352bfc0e2bb8ad0957e506c481b68a910d413e..06c01484868c90cb1d02529ff13e03990bf95226 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java @@ -14,7 +14,7 @@ package org.eclipsefoundation.git.eca.service; import java.util.List; import org.eclipsefoundation.git.eca.api.models.EclipseUser; -import org.eclipsefoundation.git.eca.model.Project; +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 55b9cb902d84cff2b63d92232e766c6e48a5bf96..0110228cebf0d2544c337cd6a2614f798fdf6836 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/ValidationService.java @@ -16,8 +16,8 @@ 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.git.eca.dto.CommitValidationStatus; -import org.eclipsefoundation.git.eca.model.Project; 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 dc4427f09ac477f376f7dac5a5e826703cbea5c3..54e841859933d0621809aa88d4f2b3d9e5790266 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 @@ -31,7 +31,7 @@ import org.eclipsefoundation.core.service.CachingService; 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.model.Project; +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 index 0fc98e656e79e59a47a9ceaacdf3af8c601fcd5e..b4c113c8ae22cc2d4791eb8ecb277958e2607cdd 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultInterestGroupService.java @@ -11,8 +11,8 @@ 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.model.InterestGroupData; -import org.eclipsefoundation.git.eca.model.Project; +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; diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultSystemHookService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultSystemHookService.java index f56aabf8a1bf5f07a3827e617d21695346b17386..394f717a485311cd7750eb6b395d039ebbdff75b 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultSystemHookService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultSystemHookService.java @@ -34,8 +34,8 @@ import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.git.eca.api.GitlabAPI; import org.eclipsefoundation.git.eca.api.models.GitlabProjectResponse; import org.eclipsefoundation.git.eca.api.models.GitlabUserResponse; +import org.eclipsefoundation.git.eca.api.models.SystemHook; import org.eclipsefoundation.git.eca.dto.PrivateProjectEvent; -import org.eclipsefoundation.git.eca.model.SystemHook; import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames; import org.eclipsefoundation.git.eca.service.SystemHookService; import org.eclipsefoundation.persistence.dao.PersistenceDao; 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 1d09d596e04432b25d2055d4a6ba9d3a22411a6d..effafd290e8399bc0573d7e935504a5a7f5b8657 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 @@ -29,13 +29,13 @@ import org.eclipsefoundation.core.helper.DateTimeHelper; 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.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.model.Commit; import org.eclipsefoundation.git.eca.model.GitUser; -import org.eclipsefoundation.git.eca.model.Project; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.model.ValidationResponse; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; 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 index a73f02afe46bd6127893e98474eca78543ff1084..d360d2e0d739a1f597175e99efcaf62e9682e937 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsService.java @@ -29,7 +29,7 @@ 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.model.Project; +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; @@ -58,6 +58,8 @@ import io.quarkus.runtime.Startup; 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; @@ -117,12 +119,14 @@ public class PaginationProjectsService implements ProjectsService { } }); - // 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."); + 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"); } - LOGGER.debug("Completed pre-cache of projects assets"); } @Override diff --git a/src/main/resources/META-INF/resources/favicon.ico b/src/main/resources/META-INF/resources/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bb43f009ecaac5bac6feda26839f664145915392 Binary files /dev/null and b/src/main/resources/META-INF/resources/favicon.ico differ diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3ffafdbffcc2a1def1397653aa514c824d1047f2..6d7f3ddad721bbbd8496c12365ea8b108a7180a2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -36,6 +36,15 @@ quarkus.cache.caffeine."default".expire-after-write=1H quarkus.cache.caffeine."record".initial-capacity=${quarkus.cache.caffeine."default".initial-capacity} quarkus.cache.caffeine."record".expire-after-write=${quarkus.cache.caffeine."default".expire-after-write} +## JWT cache, 115 second cache time to make sure there is no accidental sending of an expired token +quarkus.cache.caffeine."accesstoken".initial-capacity=1000 +quarkus.cache.caffeine."accesstoken".expire-after-write=119S +## JWT Placeholders/defaults +smallrye.jwt.new-token.lifespan=120 +smallrye.jwt.new-token.issuer=262450 +eclipse.webhooks.github.context=eclipsefdn/eca +eclipse.webhooks.github.server-target=https://api.eclipse.org + eclipse.mail.allowlist=noreply@github.com,49699333+dependabot[bot]@users.noreply.github.com %dev.eclipse.cache.resource.enabled=true %dev.eclipse.optional-resources.enabled=true diff --git a/src/test/java/org/eclipsefoundation/git/eca/resource/WebhoooksResourceTest.java b/src/test/java/org/eclipsefoundation/git/eca/resource/WebhoooksResourceTest.java index dd0f657beea8c404ed21c7e6d11f0085f6aa7fcf..23a1d312f08ed6993510273de208699eef1c70fa 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/resource/WebhoooksResourceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/resource/WebhoooksResourceTest.java @@ -16,8 +16,8 @@ import java.util.Arrays; import java.util.Map; import java.util.Optional; -import org.eclipsefoundation.git.eca.model.SystemHook; -import org.eclipsefoundation.git.eca.model.SystemHook.Owner; +import org.eclipsefoundation.git.eca.api.models.SystemHook; +import org.eclipsefoundation.git.eca.api.models.SystemHook.Owner; import org.eclipsefoundation.testing.helpers.TestCaseHelper; import org.eclipsefoundation.testing.templates.RestAssuredTemplates; import org.eclipsefoundation.testing.templates.RestAssuredTemplates.EndpointTestCase; 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 a59776fe73c882febccc5dc2044f26b00ea732ea..434fcec2bd044dff0a3de619f2649df1b2628451 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 @@ -19,7 +19,7 @@ import java.util.Optional; import javax.inject.Inject; import org.eclipsefoundation.git.eca.api.models.EclipseUser; -import org.eclipsefoundation.git.eca.model.Project; +import org.eclipsefoundation.git.eca.api.models.Project; import org.eclipsefoundation.git.eca.service.ProjectsService; import org.eclipsefoundation.git.eca.service.UserService; import org.junit.jupiter.api.Assertions; 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 index 0ccade49969a1c8c20f418d184df542225feca54..3ebbabcebc99b3cc6f9fa62801ff5139d66fafd8 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/service/impl/PaginationProjectsServiceTest.java @@ -16,8 +16,8 @@ import java.util.stream.Collectors; import javax.inject.Inject; -import org.eclipsefoundation.git.eca.model.Project; -import org.eclipsefoundation.git.eca.model.Project.Repo; +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; 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 103487d5d1f899fc64fc418fe97f31636f30d898..477248204ec451030cdd24f947aad35794053bbe 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 @@ -25,12 +25,12 @@ 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.model.InterestGroupData; -import org.eclipsefoundation.git.eca.model.InterestGroupData.Descriptor; -import org.eclipsefoundation.git.eca.model.Project; -import org.eclipsefoundation.git.eca.model.Project.GitlabProject; -import org.eclipsefoundation.git.eca.model.Project.Repo; -import org.eclipsefoundation.git.eca.model.Project.User; +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.GitlabProject; +import org.eclipsefoundation.git.eca.api.models.Project.Repo; +import org.eclipsefoundation.git.eca.api.models.Project.User; import io.quarkus.test.Mock; diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 78bcff0f9e879446d9ab2f2e3e2963e40106041b..b127c9e09da4a30f2e6d6eb2712594270b831a69 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -25,3 +25,4 @@ quarkus.http.port=8080 quarkus.oidc.enabled=false quarkus.keycloak.devservices.enabled=false quarkus.oidc-client.enabled=false +smallrye.jwt.sign.key.location=test.pem \ No newline at end of file