diff --git a/config/mariadb/initdb.d/init.sql b/config/mariadb/initdb.d/init.sql index c111da6fd21e422561c93d8cb21484d8c89e82a7..8cfeabee764ee951fe8b9291165b5b110fd68467 100644 --- a/config/mariadb/initdb.d/init.sql +++ b/config/mariadb/initdb.d/init.sql @@ -55,4 +55,15 @@ CREATE TABLE GithubWebhookTracking ( needsRevalidation tinyint(1) DEFAULT 0, manualRevalidationCount int DEFAULT 0, PRIMARY KEY (id) -); \ No newline at end of file +); + +-- eclipse_eca.GithubApplicationInstallation definition + +CREATE TABLE `GithubApplicationInstallation` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `appId` int(11) NOT NULL, + `installationId` int(11) NOT NULL, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `lastUpdated` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=389 DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8358b7553e8bf4d8c9de9a17a66d387f0586e0b4..a3eec59f652ed64e61848b438af07af9908da58c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,198 +1,210 @@ <?xml version="1.0"?> <project - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" - xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.eclipsefoundation</groupId> - <artifactId>git-eca</artifactId> - <version>1.1.0</version> - <properties> - <compiler-plugin.version>3.11.0</compiler-plugin.version> - <maven.compiler.source>17</maven.compiler.source> - <maven.compiler.target>17</maven.compiler.target> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> - <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> - <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id> - <quarkus.platform.version>3.2.11.Final</quarkus.platform.version> - <surefire-plugin.version>3.1.2</surefire-plugin.version> - <maven.compiler.parameters>true</maven.compiler.parameters> - <eclipse-api-version>0.9.5</eclipse-api-version> - <auto-value.version>1.10.4</auto-value.version> - <org.mapstruct.version>1.5.5.Final</org.mapstruct.version> - <sonar.sources>src/main</sonar.sources> - <sonar.tests>src/test</sonar.tests> - <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> - <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>git-eca-rest-api</sonar.projectKey> - <sonar.projectName>Git ECA REST API</sonar.projectName> - </properties> - <repositories> - <repository> - <id>eclipsefdn</id> - <url>https://repo.eclipse.org/content/repositories/eclipsefdn/</url> - <releases> - <enabled>true</enabled> - </releases> - <snapshots> - <enabled>true</enabled> - </snapshots> - </repository> - </repositories> - <dependencyManagement> + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.eclipsefoundation</groupId> + <artifactId>git-eca</artifactId> + <version>1.1.0</version> + <properties> + <compiler-plugin.version>3.11.0</compiler-plugin.version> + <maven.compiler.source>17</maven.compiler.source> + <maven.compiler.target>17</maven.compiler.target> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> + <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id> + <quarkus.platform.version>3.2.11.Final</quarkus.platform.version> + <surefire-plugin.version>3.1.2</surefire-plugin.version> + <maven.compiler.parameters>true</maven.compiler.parameters> + <eclipse-api-version>0.9.5</eclipse-api-version> + <auto-value.version>1.10.4</auto-value.version> + <org.mapstruct.version>1.5.5.Final</org.mapstruct.version> + <sonar.sources>src/main</sonar.sources> + <sonar.tests>src/test</sonar.tests> + <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> + <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>git-eca-rest-api</sonar.projectKey> + <sonar.projectName>Git ECA REST API</sonar.projectName> + </properties> + <repositories> + <repository> + <id>eclipsefdn</id> + <url>https://repo.eclipse.org/content/repositories/eclipsefdn/</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + </repositories> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>${quarkus.platform.group-id}</groupId> + <artifactId>${quarkus.platform.artifact-id}</artifactId> + <version>${quarkus.platform.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> <dependencies> - <dependency> - <groupId>${quarkus.platform.group-id}</groupId> - <artifactId>${quarkus.platform.artifact-id}</artifactId> - <version>${quarkus.platform.version}</version> - <type>pom</type> - <scope>import</scope> - </dependency> - </dependencies> - </dependencyManagement> - <dependencies> - <dependency> - <groupId>org.eclipsefoundation</groupId> - <artifactId>quarkus-core</artifactId> - <version>${eclipse-api-version}</version> - </dependency> - <dependency> - <groupId>org.eclipsefoundation</groupId> - <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-smallrye-context-propagation</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-smallrye-jwt</artifactId> - </dependency> + <dependency> + <groupId>org.eclipsefoundation</groupId> + <artifactId>quarkus-core</artifactId> + <version>${eclipse-api-version}</version> + </dependency> + <dependency> + <groupId>org.eclipsefoundation</groupId> + <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-smallrye-context-propagation</artifactId> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-smallrye-jwt</artifactId> + </dependency> - <!-- Required for PKCS1 compatibility --> - <dependency> - <groupId>org.bouncycastle</groupId> - <artifactId>bcprov-jdk18on</artifactId> - </dependency> - <dependency> - <groupId>org.bouncycastle</groupId> - <artifactId>bcpkix-jdk18on</artifactId> - </dependency> + <!-- Required for PKCS1 compatibility --> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk18on</artifactId> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk18on</artifactId> + </dependency> - <!-- Annotation preprocessors - reduce all of the boiler plate --> - <dependency> - <groupId>com.google.auto.value</groupId> - <artifactId>auto-value</artifactId> - <version>${auto-value.version}</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>org.mapstruct</groupId> - <artifactId>mapstruct</artifactId> - <version>${org.mapstruct.version}</version> - </dependency> - <dependency> - <groupId>org.mapstruct</groupId> - <artifactId>mapstruct-processor</artifactId> - <version>${org.mapstruct.version}</version> - <scope>provided</scope> - </dependency> + <!-- Annotation preprocessors - reduce all of the boiler plate --> + <dependency> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <version>${auto-value.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + <version>${org.mapstruct.version}</version> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>${org.mapstruct.version}</version> + <scope>provided</scope> + </dependency> - <!-- Test requirements --> - <dependency> - <groupId>org.eclipsefoundation</groupId> - <artifactId>quarkus-test-common</artifactId> - <version>${eclipse-api-version}</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-jacoco</artifactId> - <scope>test</scope> - </dependency> + <!-- Test requirements --> + <dependency> + <groupId>org.eclipsefoundation</groupId> + <artifactId>quarkus-test-common</artifactId> + <version>${eclipse-api-version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-jacoco</artifactId> + <scope>test</scope> + </dependency> - <!-- Following H2/devservices deps are made to circumvent need for docker --> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-devservices-h2</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-jdbc-h2</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>com.h2database</groupId> - <artifactId>h2</artifactId> - <scope>test</scope> - </dependency> - <!-- Flyway specific dependencies, used to setup tables in test --> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-flyway</artifactId> - <scope>test</scope> - </dependency> - </dependencies> + <!-- Following H2/devservices deps are made to circumvent need for + docker --> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-devservices-h2</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-jdbc-h2</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <scope>test</scope> + </dependency> + <!-- Flyway specific dependencies, used to setup tables in test --> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-flyway</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-test-security</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-test-security-oidc</artifactId> + <scope>test</scope> + </dependency> + </dependencies> - <build> - <plugins> - <plugin> - <groupId>${quarkus.platform.group-id}</groupId> - <artifactId>quarkus-maven-plugin</artifactId> - <version>${quarkus.platform.version}</version> - <extensions>true</extensions> - <executions> - <execution> - <goals> - <goal>build</goal> - <goal>generate-code</goal> - <goal>generate-code-tests</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <artifactId>maven-compiler-plugin</artifactId> - <version>${compiler-plugin.version}</version> - <configuration> - <annotationProcessorPaths> - <path> - <groupId>com.google.auto.value</groupId> - <artifactId>auto-value</artifactId> - <version>${auto-value.version}</version> - </path> - <path> - <groupId>org.mapstruct</groupId> - <artifactId>mapstruct-processor</artifactId> - <version>${org.mapstruct.version}</version> - </path> - </annotationProcessorPaths> - </configuration> - </plugin> - <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <version>${surefire-plugin.version}</version> - <configuration> - <skipTests>false</skipTests> - <systemPropertyVariables> - <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> - <maven.home>${maven.home}</maven.home> - </systemPropertyVariables> - </configuration> - </plugin> - </plugins> - </build> + <build> + <plugins> + <plugin> + <groupId>${quarkus.platform.group-id}</groupId> + <artifactId>quarkus-maven-plugin</artifactId> + <version>${quarkus.platform.version}</version> + <extensions>true</extensions> + <executions> + <execution> + <goals> + <goal>build</goal> + <goal>generate-code</goal> + <goal>generate-code-tests</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <version>${compiler-plugin.version}</version> + <configuration> + <annotationProcessorPaths> + <path> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <version>${auto-value.version}</version> + </path> + <path> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>${org.mapstruct.version}</version> + </path> + </annotationProcessorPaths> + </configuration> + </plugin> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire-plugin.version}</version> + <configuration> + <skipTests>false</skipTests> + <systemPropertyVariables> + <java.util.logging.manager> + org.jboss.logmanager.LogManager</java.util.logging.manager> + <maven.home>${maven.home}</maven.home> + </systemPropertyVariables> + </configuration> + </plugin> + </plugins> + </build> </project> diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubAdjacentResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/CommonResource.java similarity index 62% rename from src/main/java/org/eclipsefoundation/git/eca/resource/GithubAdjacentResource.java rename to src/main/java/org/eclipsefoundation/git/eca/resource/CommonResource.java index 5e8c79e751f2e3ae34d2fb66c94a39b11b173a9c..ac1ee2399fd8afaeb8457198e43e02f5c9af6f69 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubAdjacentResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/CommonResource.java @@ -13,23 +13,29 @@ package org.eclipsefoundation.git.eca.resource; import java.util.Arrays; -import jakarta.inject.Inject; - import org.eclipsefoundation.core.helper.DateTimeHelper; import org.eclipsefoundation.core.model.RequestWrapper; +import org.eclipsefoundation.git.eca.api.models.EclipseUser; import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking; +import org.eclipsefoundation.git.eca.service.UserService; import org.eclipsefoundation.persistence.dao.PersistenceDao; import org.eclipsefoundation.persistence.model.RDBMSQuery; import org.eclipsefoundation.persistence.service.FilterService; +import io.quarkus.security.identity.SecurityIdentity; +import io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipal; +import jakarta.inject.Inject; + /** - * Contains operations and properties that are common to resources that interact with Github validation. + * Contains operations and properties that are common to resources that interact with Github validation and user accounts. * * @author Martin Lowe * */ -public abstract class GithubAdjacentResource { +public abstract class CommonResource { + @Inject + UserService users; @Inject PersistenceDao dao; @Inject @@ -37,10 +43,22 @@ public abstract class GithubAdjacentResource { @Inject RequestWrapper wrapper; + @Inject + SecurityIdentity ident; void setRevalidationFlagForTracking(GithubWebhookTracking tracking) { tracking.setNeedsRevalidation(true); tracking.setLastUpdated(DateTimeHelper.now()); dao.add(new RDBMSQuery<>(wrapper, filters.get(GithubWebhookTracking.class)), Arrays.asList(tracking)); } + + EclipseUser getUserForLoggedInAccount() { + if (ident.isAnonymous()) { + return null; + } + // cast to a principal w/ access to claims + DefaultJWTCallerPrincipal defaultPrin = (DefaultJWTCallerPrincipal) ident.getPrincipal(); + // get the user account linked to the current claim + return users.getUser(defaultPrin.getClaim("email")); + } } diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java index 9fbe8f7da556734a5c6aba6abe1a0e96823e048b..98aafc28a1fd5dc548fb91341b9a2f8889fd60dc 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/GithubWebhooksResource.java @@ -53,7 +53,7 @@ import jakarta.ws.rs.core.Response.Status; * */ @Path("webhooks/github") -public class GithubWebhooksResource extends GithubAdjacentResource { +public class GithubWebhooksResource extends CommonResource { private static final Logger LOGGER = LoggerFactory.getLogger(GithubWebhooksResource.class); @Inject diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/OIDCResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/OIDCResource.java new file mode 100644 index 0000000000000000000000000000000000000000..043e36df7959dc7c70cf95367e07b52f7600a282 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/OIDCResource.java @@ -0,0 +1,22 @@ +package org.eclipsefoundation.git.eca.resource; + +import java.net.URI; + +import io.quarkus.security.Authenticated; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Response; + +/** + * Create a basic endpoint for enabling logins with redirects. + */ +@Authenticated +@Path("login") +public class OIDCResource { + + @GET + public Response login(@QueryParam("redirect") URI redirect) { + return Response.status(302).location(redirect).build(); + } +} 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 71a1e17ccef7275ebffa18893b1f3ea2c68b2062..48903cc7f687d0be3f837ce1b5955ea01fd14687 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/StatusResource.java @@ -38,6 +38,7 @@ import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; /** * REST resource containing endpoints related to checking the status of validation requests. @@ -46,7 +47,7 @@ import jakarta.ws.rs.core.Response; * */ @Path("eca/status") -public class StatusResource extends GithubAdjacentResource { +public class StatusResource extends CommonResource { // parameter names for the status error page private static final String INCLUDE_INSTALL_LINK_PARAMETER = "includeInstallLink"; @@ -66,6 +67,9 @@ public class StatusResource extends GithubAdjacentResource { @Inject ProjectHelper projects; + @Inject + UriInfo info; + // Qute templates, generates UI status page @Location("simple_fingerprint_ui") Template statusUiTemplate; @@ -85,8 +89,7 @@ public class StatusResource extends GithubAdjacentResource { } /** - * Retrieves commit status information based on the fingerprint and builds a UI around the results for easier - * consumption. + * Retrieves commit status information based on the fingerprint and builds a UI around the results for easier consumption. * * @param fingerprint the string associated with the request for looking up related commit statuses. * @return the HTML UI for the status of the fingerprint request @@ -109,13 +112,15 @@ public class StatusResource extends GithubAdjacentResource { .data("project", ps.isEmpty() ? null : ps.get(0)) .data(REPO_URL_PARAMETER, statuses.get(0).getRepoUrl()) .data("installationId", null) + .data("currentUser", getUserForLoggedInAccount()) + .data("redirectUri", info.getPath()) .render()) .build(); } /** - * Retrieves and checks the validity of the commit statuses for a Github pull request, and if out of date will - * revalidate the request automatically on load. + * Retrieves and checks the validity of the commit statuses for a Github pull request, and if out of date will revalidate the request + * automatically on load. * * @param org the organization in Github that contains the target repo * @param repoName the name of the repo in Github containing the pull request @@ -150,8 +155,7 @@ public class StatusResource extends GithubAdjacentResource { // get the data about the current request, and check that we have some validation statuses ValidationRequest req = validationHelper.generateRequest(installationId, repoFullName, prNo, repoUrl); List<CommitValidationStatus> statuses = validationStatus - .getHistoricValidationStatusByShas(wrapper, - req.getCommits().stream().map(Commit::getHash).toList()); + .getHistoricValidationStatusByShas(wrapper, req.getCommits().stream().map(Commit::getHash).toList()); // check if we have any data, and if there is none, attempt to validate the request information if (statuses.isEmpty()) { // run the validation for the current request adhoc @@ -170,8 +174,7 @@ public class StatusResource extends GithubAdjacentResource { } // retrieve the status of the commits to display on the status page - statuses = validationStatus - .getHistoricValidationStatusByShas(wrapper, r.getCommits().keySet().stream().toList()); + statuses = validationStatus.getHistoricValidationStatusByShas(wrapper, r.getCommits().keySet().stream().toList()); } // get projects for use in queries + UI @@ -186,6 +189,8 @@ public class StatusResource extends GithubAdjacentResource { .data("project", ps.isEmpty() ? null : ps.get(0)) .data(REPO_URL_PARAMETER, repoUrl) .data("installationId", installationId) + .data("currentUser", getUserForLoggedInAccount()) + .data("redirectUri", info.getPath()) .render()) .build(); } catch (BadRequestException e) { @@ -212,4 +217,5 @@ public class StatusResource extends GithubAdjacentResource { .build(); } } + } 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 d2bb2ed1a71c1f651986a1e4dbc5469391975390..a32f36b7c8261315e1f5a6d662c6cb63dfe3da4d 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java @@ -16,67 +16,60 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import jakarta.inject.Inject; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.NotFoundException; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; - import org.apache.commons.lang3.StringUtils; import org.eclipsefoundation.core.exception.FinalForbiddenException; import org.eclipsefoundation.core.helper.TransformationHelper; -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.UserService; import org.eclipsefoundation.git.eca.service.ValidationService; import org.jboss.resteasy.annotations.jaxrs.QueryParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.quarkus.security.Authenticated; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + /** - * 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. + * 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 */ @Path("/eca") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) -public class ValidationResource { +public class ValidationResource extends CommonResource { private static final Logger LOGGER = LoggerFactory.getLogger(ValidationResource.class); - @Inject - RequestWrapper wrapper; - // external API/service harnesses @Inject CachingService cache; @Inject ProjectHelper projects; @Inject - UserService users; - @Inject ValidationService validation; /** - * 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. */ @POST public Response validate(ValidationRequest req) { @@ -95,7 +88,14 @@ public class ValidationResource { @GET @Path("/lookup") + @Authenticated public Response getUserStatus(@QueryParam("email") String email) { + // check that the user has committer level access + EclipseUser loggedInUser = getUserForLoggedInAccount(); + if (loggedInUser == null || !loggedInUser.getIsCommitter()) { + throw new FinalForbiddenException("User must be logged in and have committer level access to use this endpoint."); + } + // do the lookup of the passed email EclipseUser user = users.getUser(email); if (Objects.isNull(user)) { throw new NotFoundException(String.format("No user found with mail '%s'", TransformationHelper.formatLog(email))); @@ -108,8 +108,7 @@ public class ValidationResource { } /** - * 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. diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 92e68594ce659190fc527833c2ea34e990b66be4..f40b05a96e5e03284274971f812e842d1fb2e0d4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -21,8 +21,11 @@ quarkus.hibernate-orm.packages=org.eclipsefoundation.git.eca.dto quarkus.hibernate-orm.datasource=<default> ## Security configs -quarkus.oauth2.enabled=false -quarkus.oidc.enabled=false +quarkus.oidc.application-type=web-app +quarkus.oidc.token.refresh-expired=true +quarkus.oidc.authentication.session-age-extension=60m +quarkus.oidc.discovery-enabled=true +quarkus.oidc.roles.source=accesstoken eclipse.security.oauth2.token-generation.scope=eclipsefdn_view_all_profiles eclipse.security.oauth2.token-generation.client-id=placeholder eclipse.security.oauth2.token-generation.client-secret=placeholder diff --git a/src/main/resources/templates/simple_fingerprint_ui.html b/src/main/resources/templates/simple_fingerprint_ui.html index 932e7027c34f8c21bfe2c00efec898c973ac57b8..a84fd87568fff5412371467670997fc8adfae0a6 100644 --- a/src/main/resources/templates/simple_fingerprint_ui.html +++ b/src/main/resources/templates/simple_fingerprint_ui.html @@ -179,6 +179,7 @@ <section id="block-site-login-eclipse-eca-sle-eca-lookup-tool" class="margin-bottom-30 clearfix"> <h2>ECA Validation Tool</h2> + {#if currentUser and currentUser.isCommitter} <form id="eclipse-eca-lookup-form" accept-charset="UTF-8"> <div class="form-item form-item-input form-type-textfield form-group"> <input placeholder="Enter email address" class="form-control form-text" type="text" id="email-input" @@ -187,6 +188,12 @@ </div> <button class="btn-success btn form-submit" type="submit" id="edit-submit">Verify ECA</button> </form> + {#else if currentUser} + <p>Logged in users must have committer level access to use the ECA Validation tool.</p> + {#else} + <p>Please login to use the ECA Validation tool. Please note that only committers are able to use this tool.</p> + <a class="btn btn-primary" href="/git/login?redirect={redirectUri}">Login</a> + {/if} </section> <section id="block-eclipse-api-github-eclipse-api-github-links" class="main-sidebar-default-margin"> <ul id="leftnav" class="ul-left-nav fa-ul hidden-print" role="tablist"> diff --git a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java index 315f9d2ee9706becb19ce3f79c411af0d3bc226e..ba26d06fb6c1b6af6197f98c38e4ef6f43ad0b35 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import jakarta.inject.Inject; - import org.eclipsefoundation.core.exception.ApplicationException; import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.git.eca.model.Commit; @@ -33,8 +31,8 @@ import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; import org.eclipsefoundation.git.eca.namespace.ProviderType; import org.eclipsefoundation.git.eca.test.namespaces.SchemaNamespaceHelper; +import org.eclipsefoundation.testing.helpers.AuthHelper; import org.eclipsefoundation.testing.helpers.TestCaseHelper; - import org.eclipsefoundation.testing.models.EndpointTestBuilder; import org.eclipsefoundation.testing.models.EndpointTestCase; import org.junit.jupiter.api.Assertions; @@ -45,10 +43,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.quarkus.test.security.oidc.Claim; +import io.quarkus.test.security.oidc.OidcSecurity; +import jakarta.inject.Inject; +import jakarta.ws.rs.core.Response.Status; /** - * Tests for verifying end to end validation via the endpoint. Uses restassured to create pseudo requests, and Mock API - * endpoints to ensure that all data is kept internal for test checks. + * Tests for verifying end to end validation via the endpoint. Uses restassured to create pseudo requests, and Mock API endpoints to ensure + * that all data is kept internal for test checks. * * @author Martin Lowe * @author Zachary Sabourin @@ -115,6 +118,10 @@ class ValidationResourceTest { */ public static final EndpointTestCase LOOKUP_SUCCESS_CASE = TestCaseHelper .buildSuccessCase(LOOKUP_URL, new String[] { "slom@eclipse-foundation.org" }, ""); + public static final EndpointTestCase LOOKUP_ANONYMOUS_CASE = TestCaseHelper + .prepareTestCase(LOOKUP_URL, new String[] { "slom@eclipse-foundation.org" }, "") + .setStatusCode(Status.UNAUTHORIZED.getStatusCode()) + .build(); public static final EndpointTestCase LOOKUP_FORBIDDEN_CASE = TestCaseHelper .buildForbiddenCase(LOOKUP_URL, new String[] { "newbie@important.co" }, ""); public static final EndpointTestCase LOOKUP_NOT_FOUND_CASE = TestCaseHelper @@ -869,16 +876,34 @@ class ValidationResourceTest { * USER LOOKUP TESTS */ @Test + void validateUserLookup_failure_anonymous() { + EndpointTestBuilder.from(LOOKUP_ANONYMOUS_CASE).run(); + } + + @Test + @TestSecurity(user = "newbieAnon", roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "newbie@important.co") }) + void validateUserLookup_failure_nonCommitter() { + EndpointTestBuilder.from(LOOKUP_FORBIDDEN_CASE).run(); + } + + @Test + @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) void validateUserLookup_userNotFound() { EndpointTestBuilder.from(LOOKUP_NOT_FOUND_CASE).run(); } @Test + @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) void validateUserLookup_userNoECA() { EndpointTestBuilder.from(LOOKUP_FORBIDDEN_CASE).run(); } @Test + @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) void validateUserLookup_userSuccess() { EndpointTestBuilder.from(LOOKUP_NOT_FOUND_CASE).run(); } diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java index ef1d5b0729c2fc4516d3acbf165bb9605b8fbc70..69ca77bc6bf89cef43eb904107d78546b6075dbf 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java @@ -26,8 +26,8 @@ import org.eclipsefoundation.git.eca.api.models.EclipseUser.ECA; import io.quarkus.test.Mock; /** - * Simple stub for accounts API. Allows for easy testing of users that don't really exist upstream, and so that we don't - * need a real auth token for data. + * Simple stub for accounts API. Allows for easy testing of users that don't really exist upstream, and so that we don't need a real auth + * token for data. * * @author Martin Lowe * @@ -104,7 +104,18 @@ public class MockAccountsAPI implements AccountsAPI { .setName("sumAnalyst") .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) .build()); - + users + .put("opearson@important.co", + EclipseUser + .builder() + .setIsCommitter(true) + .setUid(id++) + .setMail("opearson@important.co") + .setName("opearson") + .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) + .setGithubHandle("opearson") + .setIsCommitter(true) + .build()); } @Override