Skip to content
Snippets Groups Projects
Commit 45c17bc2 authored by Martin Lowe's avatar Martin Lowe :flag_ca:
Browse files

Merge branch 'malowe/main/75' into 'main'

Iss #75 - Add authentication protection to the user lookup endpoint

See merge request eclipsefdn/it/api/git-eca-rest-api!184
parents aa461607 d472d0a1
Branches main
No related tags found
No related merge requests found
Showing
with 357 additions and 243 deletions
...@@ -55,4 +55,15 @@ CREATE TABLE GithubWebhookTracking ( ...@@ -55,4 +55,15 @@ CREATE TABLE GithubWebhookTracking (
needsRevalidation tinyint(1) DEFAULT 0, needsRevalidation tinyint(1) DEFAULT 0,
manualRevalidationCount int DEFAULT 0, manualRevalidationCount int DEFAULT 0,
PRIMARY KEY (id) 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
<?xml version="1.0"?> <?xml version="1.0"?>
<project <project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" 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="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.eclipsefoundation</groupId> <groupId>org.eclipsefoundation</groupId>
<artifactId>git-eca</artifactId> <artifactId>git-eca</artifactId>
<version>1.1.0</version> <version>1.1.0</version>
<properties> <properties>
<compiler-plugin.version>3.11.0</compiler-plugin.version> <compiler-plugin.version>3.11.0</compiler-plugin.version>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id> <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.2.11.Final</quarkus.platform.version> <quarkus.platform.version>3.2.11.Final</quarkus.platform.version>
<surefire-plugin.version>3.1.2</surefire-plugin.version> <surefire-plugin.version>3.1.2</surefire-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters> <maven.compiler.parameters>true</maven.compiler.parameters>
<eclipse-api-version>0.9.5</eclipse-api-version> <eclipse-api-version>0.9.5</eclipse-api-version>
<auto-value.version>1.10.4</auto-value.version> <auto-value.version>1.10.4</auto-value.version>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version> <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
<sonar.sources>src/main</sonar.sources> <sonar.sources>src/main</sonar.sources>
<sonar.tests>src/test</sonar.tests> <sonar.tests>src/test</sonar.tests>
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
<sonar.coverage.jacoco.xmlReportPaths> <sonar.coverage.jacoco.xmlReportPaths>
${project.basedir}/target/jacoco-report/jacoco.xml</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.junit.reportPath>${project.build.directory}/surefire-reports</sonar.junit.reportPath>
<sonar.host.url>https://sonarcloud.io</sonar.host.url> <sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>eclipse-foundation-it</sonar.organization> <sonar.organization>eclipse-foundation-it</sonar.organization>
<sonar.projectKey>git-eca-rest-api</sonar.projectKey> <sonar.projectKey>git-eca-rest-api</sonar.projectKey>
<sonar.projectName>Git ECA REST API</sonar.projectName> <sonar.projectName>Git ECA REST API</sonar.projectName>
</properties> </properties>
<repositories> <repositories>
<repository> <repository>
<id>eclipsefdn</id> <id>eclipsefdn</id>
<url>https://repo.eclipse.org/content/repositories/eclipsefdn/</url> <url>https://repo.eclipse.org/content/repositories/eclipsefdn/</url>
<releases> <releases>
<enabled>true</enabled> <enabled>true</enabled>
</releases> </releases>
<snapshots> <snapshots>
<enabled>true</enabled> <enabled>true</enabled>
</snapshots> </snapshots>
</repository> </repository>
</repositories> </repositories>
<dependencyManagement> <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> <dependencies>
<dependency> <dependency>
<groupId>${quarkus.platform.group-id}</groupId> <groupId>org.eclipsefoundation</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId> <artifactId>quarkus-core</artifactId>
<version>${quarkus.platform.version}</version> <version>${eclipse-api-version}</version>
<type>pom</type> </dependency>
<scope>import</scope> <dependency>
</dependency> <groupId>org.eclipsefoundation</groupId>
</dependencies> <artifactId>quarkus-persistence</artifactId>
</dependencyManagement> <version>${eclipse-api-version}</version>
<dependencies> </dependency>
<dependency> <dependency>
<groupId>org.eclipsefoundation</groupId> <groupId>org.eclipsefoundation</groupId>
<artifactId>quarkus-core</artifactId> <artifactId>quarkus-efservices</artifactId>
<version>${eclipse-api-version}</version> <version>${eclipse-api-version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipsefoundation</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-persistence</artifactId> <artifactId>quarkus-smallrye-context-propagation</artifactId>
<version>${eclipse-api-version}</version> </dependency>
</dependency> <dependency>
<dependency> <groupId>io.quarkus</groupId>
<groupId>org.eclipsefoundation</groupId> <artifactId>quarkus-smallrye-jwt</artifactId>
<artifactId>quarkus-efservices</artifactId> </dependency>
<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 --> <!-- Required for PKCS1 compatibility -->
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId> <artifactId>bcprov-jdk18on</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId> <artifactId>bcpkix-jdk18on</artifactId>
</dependency> </dependency>
<!-- Annotation preprocessors - reduce all of the boiler plate --> <!-- Annotation preprocessors - reduce all of the boiler plate -->
<dependency> <dependency>
<groupId>com.google.auto.value</groupId> <groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId> <artifactId>auto-value</artifactId>
<version>${auto-value.version}</version> <version>${auto-value.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId> <artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version> <version>${org.mapstruct.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId> <artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version> <version>${org.mapstruct.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Test requirements --> <!-- Test requirements -->
<dependency> <dependency>
<groupId>org.eclipsefoundation</groupId> <groupId>org.eclipsefoundation</groupId>
<artifactId>quarkus-test-common</artifactId> <artifactId>quarkus-test-common</artifactId>
<version>${eclipse-api-version}</version> <version>${eclipse-api-version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-jacoco</artifactId> <artifactId>quarkus-jacoco</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- Following H2/devservices deps are made to circumvent need for docker --> <!-- Following H2/devservices deps are made to circumvent need for
<dependency> docker -->
<groupId>io.quarkus</groupId> <dependency>
<artifactId>quarkus-devservices-h2</artifactId> <groupId>io.quarkus</groupId>
<scope>test</scope> <artifactId>quarkus-devservices-h2</artifactId>
</dependency> <scope>test</scope>
<dependency> </dependency>
<groupId>io.quarkus</groupId> <dependency>
<artifactId>quarkus-jdbc-h2</artifactId> <groupId>io.quarkus</groupId>
<scope>test</scope> <artifactId>quarkus-jdbc-h2</artifactId>
</dependency> <scope>test</scope>
<dependency> </dependency>
<groupId>com.h2database</groupId> <dependency>
<artifactId>h2</artifactId> <groupId>com.h2database</groupId>
<scope>test</scope> <artifactId>h2</artifactId>
</dependency> <scope>test</scope>
<!-- Flyway specific dependencies, used to setup tables in test --> </dependency>
<dependency> <!-- Flyway specific dependencies, used to setup tables in test -->
<groupId>io.quarkus</groupId> <dependency>
<artifactId>quarkus-flyway</artifactId> <groupId>io.quarkus</groupId>
<scope>test</scope> <artifactId>quarkus-flyway</artifactId>
</dependency> <scope>test</scope>
</dependencies> </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> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>${quarkus.platform.group-id}</groupId> <groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId> <artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version> <version>${quarkus.platform.version}</version>
<extensions>true</extensions> <extensions>true</extensions>
<executions> <executions>
<execution> <execution>
<goals> <goals>
<goal>build</goal> <goal>build</goal>
<goal>generate-code</goal> <goal>generate-code</goal>
<goal>generate-code-tests</goal> <goal>generate-code-tests</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version> <version>${compiler-plugin.version}</version>
<configuration> <configuration>
<annotationProcessorPaths> <annotationProcessorPaths>
<path> <path>
<groupId>com.google.auto.value</groupId> <groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId> <artifactId>auto-value</artifactId>
<version>${auto-value.version}</version> <version>${auto-value.version}</version>
</path> </path>
<path> <path>
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId> <artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version> <version>${org.mapstruct.version}</version>
</path> </path>
</annotationProcessorPaths> </annotationProcessorPaths>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version> <version>${surefire-plugin.version}</version>
<configuration> <configuration>
<skipTests>false</skipTests> <skipTests>false</skipTests>
<systemPropertyVariables> <systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> <java.util.logging.manager>
<maven.home>${maven.home}</maven.home> org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemPropertyVariables> <maven.home>${maven.home}</maven.home>
</configuration> </systemPropertyVariables>
</plugin> </configuration>
</plugins> </plugin>
</build> </plugins>
</build>
</project> </project>
...@@ -13,23 +13,29 @@ package org.eclipsefoundation.git.eca.resource; ...@@ -13,23 +13,29 @@ package org.eclipsefoundation.git.eca.resource;
import java.util.Arrays; import java.util.Arrays;
import jakarta.inject.Inject;
import org.eclipsefoundation.core.helper.DateTimeHelper; import org.eclipsefoundation.core.helper.DateTimeHelper;
import org.eclipsefoundation.core.model.RequestWrapper; 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.dto.GithubWebhookTracking;
import org.eclipsefoundation.git.eca.service.UserService;
import org.eclipsefoundation.persistence.dao.PersistenceDao; import org.eclipsefoundation.persistence.dao.PersistenceDao;
import org.eclipsefoundation.persistence.model.RDBMSQuery; import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.eclipsefoundation.persistence.service.FilterService; 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 * @author Martin Lowe
* *
*/ */
public abstract class GithubAdjacentResource { public abstract class CommonResource {
@Inject
UserService users;
@Inject @Inject
PersistenceDao dao; PersistenceDao dao;
@Inject @Inject
...@@ -37,10 +43,22 @@ public abstract class GithubAdjacentResource { ...@@ -37,10 +43,22 @@ public abstract class GithubAdjacentResource {
@Inject @Inject
RequestWrapper wrapper; RequestWrapper wrapper;
@Inject
SecurityIdentity ident;
void setRevalidationFlagForTracking(GithubWebhookTracking tracking) { void setRevalidationFlagForTracking(GithubWebhookTracking tracking) {
tracking.setNeedsRevalidation(true); tracking.setNeedsRevalidation(true);
tracking.setLastUpdated(DateTimeHelper.now()); tracking.setLastUpdated(DateTimeHelper.now());
dao.add(new RDBMSQuery<>(wrapper, filters.get(GithubWebhookTracking.class)), Arrays.asList(tracking)); 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"));
}
} }
...@@ -53,7 +53,7 @@ import jakarta.ws.rs.core.Response.Status; ...@@ -53,7 +53,7 @@ import jakarta.ws.rs.core.Response.Status;
* *
*/ */
@Path("webhooks/github") @Path("webhooks/github")
public class GithubWebhooksResource extends GithubAdjacentResource { public class GithubWebhooksResource extends CommonResource {
private static final Logger LOGGER = LoggerFactory.getLogger(GithubWebhooksResource.class); private static final Logger LOGGER = LoggerFactory.getLogger(GithubWebhooksResource.class);
@Inject @Inject
......
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();
}
}
...@@ -38,6 +38,7 @@ import jakarta.ws.rs.PathParam; ...@@ -38,6 +38,7 @@ import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces; import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
/** /**
* REST resource containing endpoints related to checking the status of validation requests. * REST resource containing endpoints related to checking the status of validation requests.
...@@ -46,7 +47,7 @@ import jakarta.ws.rs.core.Response; ...@@ -46,7 +47,7 @@ import jakarta.ws.rs.core.Response;
* *
*/ */
@Path("eca/status") @Path("eca/status")
public class StatusResource extends GithubAdjacentResource { public class StatusResource extends CommonResource {
// parameter names for the status error page // parameter names for the status error page
private static final String INCLUDE_INSTALL_LINK_PARAMETER = "includeInstallLink"; private static final String INCLUDE_INSTALL_LINK_PARAMETER = "includeInstallLink";
...@@ -66,6 +67,9 @@ public class StatusResource extends GithubAdjacentResource { ...@@ -66,6 +67,9 @@ public class StatusResource extends GithubAdjacentResource {
@Inject @Inject
ProjectHelper projects; ProjectHelper projects;
@Inject
UriInfo info;
// Qute templates, generates UI status page // Qute templates, generates UI status page
@Location("simple_fingerprint_ui") @Location("simple_fingerprint_ui")
Template statusUiTemplate; Template statusUiTemplate;
...@@ -85,8 +89,7 @@ public class StatusResource extends GithubAdjacentResource { ...@@ -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 * Retrieves commit status information based on the fingerprint and builds a UI around the results for easier consumption.
* consumption.
* *
* @param fingerprint the string associated with the request for looking up related commit statuses. * @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 * @return the HTML UI for the status of the fingerprint request
...@@ -109,13 +112,15 @@ public class StatusResource extends GithubAdjacentResource { ...@@ -109,13 +112,15 @@ public class StatusResource extends GithubAdjacentResource {
.data("project", ps.isEmpty() ? null : ps.get(0)) .data("project", ps.isEmpty() ? null : ps.get(0))
.data(REPO_URL_PARAMETER, statuses.get(0).getRepoUrl()) .data(REPO_URL_PARAMETER, statuses.get(0).getRepoUrl())
.data("installationId", null) .data("installationId", null)
.data("currentUser", getUserForLoggedInAccount())
.data("redirectUri", info.getPath())
.render()) .render())
.build(); .build();
} }
/** /**
* Retrieves and checks the validity of the commit statuses for a Github pull request, and if out of date will * Retrieves and checks the validity of the commit statuses for a Github pull request, and if out of date will revalidate the request
* revalidate the request automatically on load. * automatically on load.
* *
* @param org the organization in Github that contains the target repo * @param org the organization in Github that contains the target repo
* @param repoName the name of the repo in Github containing the pull request * @param repoName the name of the repo in Github containing the pull request
...@@ -150,8 +155,7 @@ public class StatusResource extends GithubAdjacentResource { ...@@ -150,8 +155,7 @@ public class StatusResource extends GithubAdjacentResource {
// get the data about the current request, and check that we have some validation statuses // get the data about the current request, and check that we have some validation statuses
ValidationRequest req = validationHelper.generateRequest(installationId, repoFullName, prNo, repoUrl); ValidationRequest req = validationHelper.generateRequest(installationId, repoFullName, prNo, repoUrl);
List<CommitValidationStatus> statuses = validationStatus List<CommitValidationStatus> statuses = validationStatus
.getHistoricValidationStatusByShas(wrapper, .getHistoricValidationStatusByShas(wrapper, req.getCommits().stream().map(Commit::getHash).toList());
req.getCommits().stream().map(Commit::getHash).toList());
// check if we have any data, and if there is none, attempt to validate the request information // check if we have any data, and if there is none, attempt to validate the request information
if (statuses.isEmpty()) { if (statuses.isEmpty()) {
// run the validation for the current request adhoc // run the validation for the current request adhoc
...@@ -170,8 +174,7 @@ public class StatusResource extends GithubAdjacentResource { ...@@ -170,8 +174,7 @@ public class StatusResource extends GithubAdjacentResource {
} }
// retrieve the status of the commits to display on the status page // retrieve the status of the commits to display on the status page
statuses = validationStatus statuses = validationStatus.getHistoricValidationStatusByShas(wrapper, r.getCommits().keySet().stream().toList());
.getHistoricValidationStatusByShas(wrapper, r.getCommits().keySet().stream().toList());
} }
// get projects for use in queries + UI // get projects for use in queries + UI
...@@ -186,6 +189,8 @@ public class StatusResource extends GithubAdjacentResource { ...@@ -186,6 +189,8 @@ public class StatusResource extends GithubAdjacentResource {
.data("project", ps.isEmpty() ? null : ps.get(0)) .data("project", ps.isEmpty() ? null : ps.get(0))
.data(REPO_URL_PARAMETER, repoUrl) .data(REPO_URL_PARAMETER, repoUrl)
.data("installationId", installationId) .data("installationId", installationId)
.data("currentUser", getUserForLoggedInAccount())
.data("redirectUri", info.getPath())
.render()) .render())
.build(); .build();
} catch (BadRequestException e) { } catch (BadRequestException e) {
...@@ -212,4 +217,5 @@ public class StatusResource extends GithubAdjacentResource { ...@@ -212,4 +217,5 @@ public class StatusResource extends GithubAdjacentResource {
.build(); .build();
} }
} }
} }
...@@ -16,67 +16,60 @@ import java.util.ArrayList; ...@@ -16,67 +16,60 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; 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.apache.commons.lang3.StringUtils;
import org.eclipsefoundation.core.exception.FinalForbiddenException; import org.eclipsefoundation.core.exception.FinalForbiddenException;
import org.eclipsefoundation.core.helper.TransformationHelper; import org.eclipsefoundation.core.helper.TransformationHelper;
import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.git.eca.api.models.EclipseUser; import org.eclipsefoundation.git.eca.api.models.EclipseUser;
import org.eclipsefoundation.git.eca.helper.ProjectHelper; import org.eclipsefoundation.git.eca.helper.ProjectHelper;
import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.model.ValidationResponse; import org.eclipsefoundation.git.eca.model.ValidationResponse;
import org.eclipsefoundation.git.eca.namespace.APIStatusCode; import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
import org.eclipsefoundation.git.eca.service.UserService;
import org.eclipsefoundation.git.eca.service.ValidationService; import org.eclipsefoundation.git.eca.service.ValidationService;
import org.jboss.resteasy.annotations.jaxrs.QueryParam; import org.jboss.resteasy.annotations.jaxrs.QueryParam;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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 * ECA validation endpoint for Git commits. Will use information from the bots, projects, and accounts API to validate commits passed to
* commits passed to this endpoint. Should be as system agnostic as possible to allow for any service to request * this endpoint. Should be as system agnostic as possible to allow for any service to request validation with less reliance on services
* validation with less reliance on services external to the Eclipse foundation. * external to the Eclipse foundation.
* *
* @author Martin Lowe, Zachary Sabourin * @author Martin Lowe, Zachary Sabourin
*/ */
@Path("/eca") @Path("/eca")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public class ValidationResource { public class ValidationResource extends CommonResource {
private static final Logger LOGGER = LoggerFactory.getLogger(ValidationResource.class); private static final Logger LOGGER = LoggerFactory.getLogger(ValidationResource.class);
@Inject
RequestWrapper wrapper;
// external API/service harnesses // external API/service harnesses
@Inject @Inject
CachingService cache; CachingService cache;
@Inject @Inject
ProjectHelper projects; ProjectHelper projects;
@Inject @Inject
UserService users;
@Inject
ValidationService validation; ValidationService validation;
/** /**
* Consuming a JSON request, this method will validate all passed commits, using the repo URL and the repository * Consuming a JSON request, this method will validate all passed commits, using the repo URL and the repository provider. These commits
* provider. These commits will be validated to ensure that all users are covered either by an ECA, or are committers on * 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
* the project. In the case of ECA-only contributors, an additional sign off footer is required in the body of the * contributors, an additional sign off footer is required in the body of the commit.
* commit.
* *
* @param req the request containing basic data plus the commits to be validated * @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 * @return a web response indicating success or failure for each commit, along with standard messages that may be used to give users
* to give users context on failure. * context on failure.
*/ */
@POST @POST
public Response validate(ValidationRequest req) { public Response validate(ValidationRequest req) {
...@@ -95,7 +88,14 @@ public class ValidationResource { ...@@ -95,7 +88,14 @@ public class ValidationResource {
@GET @GET
@Path("/lookup") @Path("/lookup")
@Authenticated
public Response getUserStatus(@QueryParam("email") String email) { 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); EclipseUser user = users.getUser(email);
if (Objects.isNull(user)) { if (Objects.isNull(user)) {
throw new NotFoundException(String.format("No user found with mail '%s'", TransformationHelper.formatLog(email))); throw new NotFoundException(String.format("No user found with mail '%s'", TransformationHelper.formatLog(email)));
...@@ -108,8 +108,7 @@ public class ValidationResource { ...@@ -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 * Check if there are any issues with the validation request, returning error messages if there are issues with the request.
* request.
* *
* @param req the current validation 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.
......
...@@ -21,8 +21,11 @@ quarkus.hibernate-orm.packages=org.eclipsefoundation.git.eca.dto ...@@ -21,8 +21,11 @@ quarkus.hibernate-orm.packages=org.eclipsefoundation.git.eca.dto
quarkus.hibernate-orm.datasource=<default> quarkus.hibernate-orm.datasource=<default>
## Security configs ## Security configs
quarkus.oauth2.enabled=false quarkus.oidc.application-type=web-app
quarkus.oidc.enabled=false 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.scope=eclipsefdn_view_all_profiles
eclipse.security.oauth2.token-generation.client-id=placeholder eclipse.security.oauth2.token-generation.client-id=placeholder
eclipse.security.oauth2.token-generation.client-secret=placeholder eclipse.security.oauth2.token-generation.client-secret=placeholder
......
...@@ -179,6 +179,7 @@ ...@@ -179,6 +179,7 @@
<section id="block-site-login-eclipse-eca-sle-eca-lookup-tool" <section id="block-site-login-eclipse-eca-sle-eca-lookup-tool"
class="margin-bottom-30 clearfix"> class="margin-bottom-30 clearfix">
<h2>ECA Validation Tool</h2> <h2>ECA Validation Tool</h2>
{#if currentUser and currentUser.isCommitter}
<form id="eclipse-eca-lookup-form" accept-charset="UTF-8"> <form id="eclipse-eca-lookup-form" accept-charset="UTF-8">
<div class="form-item form-item-input form-type-textfield form-group"> <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" <input placeholder="Enter email address" class="form-control form-text" type="text" id="email-input"
...@@ -187,6 +188,12 @@ ...@@ -187,6 +188,12 @@
</div> </div>
<button class="btn-success btn form-submit" type="submit" id="edit-submit">Verify ECA</button> <button class="btn-success btn form-submit" type="submit" id="edit-submit">Verify ECA</button>
</form> </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>
<section id="block-eclipse-api-github-eclipse-api-github-links" class="main-sidebar-default-margin"> <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"> <ul id="leftnav" class="ul-left-nav fa-ul hidden-print" role="tablist">
......
...@@ -23,8 +23,6 @@ import java.util.List; ...@@ -23,8 +23,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import jakarta.inject.Inject;
import org.eclipsefoundation.core.exception.ApplicationException; import org.eclipsefoundation.core.exception.ApplicationException;
import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.git.eca.model.Commit; import org.eclipsefoundation.git.eca.model.Commit;
...@@ -33,8 +31,8 @@ import org.eclipsefoundation.git.eca.model.ValidationRequest; ...@@ -33,8 +31,8 @@ import org.eclipsefoundation.git.eca.model.ValidationRequest;
import org.eclipsefoundation.git.eca.namespace.APIStatusCode; import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
import org.eclipsefoundation.git.eca.namespace.ProviderType; import org.eclipsefoundation.git.eca.namespace.ProviderType;
import org.eclipsefoundation.git.eca.test.namespaces.SchemaNamespaceHelper; import org.eclipsefoundation.git.eca.test.namespaces.SchemaNamespaceHelper;
import org.eclipsefoundation.testing.helpers.AuthHelper;
import org.eclipsefoundation.testing.helpers.TestCaseHelper; import org.eclipsefoundation.testing.helpers.TestCaseHelper;
import org.eclipsefoundation.testing.models.EndpointTestBuilder; import org.eclipsefoundation.testing.models.EndpointTestBuilder;
import org.eclipsefoundation.testing.models.EndpointTestCase; import org.eclipsefoundation.testing.models.EndpointTestCase;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
...@@ -45,10 +43,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; ...@@ -45,10 +43,15 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.test.junit.QuarkusTest; 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 * Tests for verifying end to end validation via the endpoint. Uses restassured to create pseudo requests, and Mock API endpoints to ensure
* endpoints to ensure that all data is kept internal for test checks. * that all data is kept internal for test checks.
* *
* @author Martin Lowe * @author Martin Lowe
* @author Zachary Sabourin * @author Zachary Sabourin
...@@ -115,6 +118,10 @@ class ValidationResourceTest { ...@@ -115,6 +118,10 @@ class ValidationResourceTest {
*/ */
public static final EndpointTestCase LOOKUP_SUCCESS_CASE = TestCaseHelper public static final EndpointTestCase LOOKUP_SUCCESS_CASE = TestCaseHelper
.buildSuccessCase(LOOKUP_URL, new String[] { "slom@eclipse-foundation.org" }, ""); .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 public static final EndpointTestCase LOOKUP_FORBIDDEN_CASE = TestCaseHelper
.buildForbiddenCase(LOOKUP_URL, new String[] { "newbie@important.co" }, ""); .buildForbiddenCase(LOOKUP_URL, new String[] { "newbie@important.co" }, "");
public static final EndpointTestCase LOOKUP_NOT_FOUND_CASE = TestCaseHelper public static final EndpointTestCase LOOKUP_NOT_FOUND_CASE = TestCaseHelper
...@@ -869,16 +876,34 @@ class ValidationResourceTest { ...@@ -869,16 +876,34 @@ class ValidationResourceTest {
* USER LOOKUP TESTS * USER LOOKUP TESTS
*/ */
@Test @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() { void validateUserLookup_userNotFound() {
EndpointTestBuilder.from(LOOKUP_NOT_FOUND_CASE).run(); EndpointTestBuilder.from(LOOKUP_NOT_FOUND_CASE).run();
} }
@Test @Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
@OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") })
void validateUserLookup_userNoECA() { void validateUserLookup_userNoECA() {
EndpointTestBuilder.from(LOOKUP_FORBIDDEN_CASE).run(); EndpointTestBuilder.from(LOOKUP_FORBIDDEN_CASE).run();
} }
@Test @Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
@OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") })
void validateUserLookup_userSuccess() { void validateUserLookup_userSuccess() {
EndpointTestBuilder.from(LOOKUP_NOT_FOUND_CASE).run(); EndpointTestBuilder.from(LOOKUP_NOT_FOUND_CASE).run();
} }
......
...@@ -26,8 +26,8 @@ import org.eclipsefoundation.git.eca.api.models.EclipseUser.ECA; ...@@ -26,8 +26,8 @@ import org.eclipsefoundation.git.eca.api.models.EclipseUser.ECA;
import io.quarkus.test.Mock; 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 * 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
* need a real auth token for data. * token for data.
* *
* @author Martin Lowe * @author Martin Lowe
* *
...@@ -104,7 +104,18 @@ public class MockAccountsAPI implements AccountsAPI { ...@@ -104,7 +104,18 @@ public class MockAccountsAPI implements AccountsAPI {
.setName("sumAnalyst") .setName("sumAnalyst")
.setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build())
.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 @Override
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment