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

Add authentication to the reports endpoint

parent 127895b9
No related branches found
No related tags found
1 merge request!116Add authentication to the reports endpoint
......@@ -60,6 +60,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-context-propagation</artifactId>
......@@ -115,6 +119,21 @@
<artifactId>rest-assured</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>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-oidc-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
......
......@@ -131,6 +131,8 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Error"
401:
description: Unauthorized - user not allowed
500:
description: Error while processing request
......
......@@ -12,6 +12,7 @@
package org.eclipsefoundation.git.eca.resource;
import java.time.LocalDate;
import java.util.List;
import javax.inject.Inject;
import javax.ws.rs.BadRequestException;
......@@ -21,23 +22,39 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
import org.eclipsefoundation.git.eca.service.ReportsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
@Authenticated
@Path("/reports")
public class ReportsResource {
private static final Logger LOGGER = LoggerFactory.getLogger(ReportsResource.class);
@ConfigProperty(name = "eclipse.reports.allowed-users")
List<String> allowedUsers;
@Inject
RequestWrapper wrap;
@Inject
ReportsService reportsService;
@Inject
SecurityIdentity ident;
@GET
@Path("/gitlab/private-projects")
public Response getPrivateProjectEvents(@QueryParam("status") String status, @QueryParam("since") LocalDate since,
@QueryParam("until") LocalDate until) {
if (!allowedUsers.contains(ident.getPrincipal().getName())) {
LOGGER.debug("User '{}' does not have access to the reports, access blocked", ident.getPrincipal().getName());
return Response.status(401).build();
}
if (StringUtils.isNotBlank(status) && !isValidStatus(status)) {
throw new BadRequestException(String.format("Invalid 'status' parameter: %s", status));
}
......
......@@ -6,8 +6,12 @@ quarkus.rest-client."org.eclipsefoundation.git.eca.api.GitlabAPI".url=https://gi
eclipse.noreply.email-patterns=@users.noreply.github.com\$
## Base HTTP settings
quarkus.http.enable-compression=true
quarkus.http.port=8080
## Expect to be mounted to '/git' to match current URL spec
quarkus.http.root-path=/git
## DATASOURCE CONFIG
eclipse.db.default.limit=10
eclipse.db.default.limit.max=100
......@@ -19,16 +23,17 @@ quarkus.datasource.jdbc.max-size = 15
quarkus.hibernate-orm.packages=org.eclipsefoundation.git.eca.dto
quarkus.hibernate-orm.datasource=<default>
quarkus.http.enable-compression=true
quarkus.http.port=8080
## OAUTH CONFIG
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
oauth2.scope=eclipsefdn_view_all_profiles
oauth2.client-id=placeholder
oauth2.client-secret=placeholder
eclipse.reports.allowed-users=mbarbaro,webdev
quarkus.cache.caffeine."default".initial-capacity=1000
quarkus.cache.caffeine."default".expire-after-write=1H
......
......@@ -13,12 +13,17 @@ package org.eclipsefoundation.git.eca.resource;
import org.eclipsefoundation.git.eca.namespace.GitEcaParameterNames;
import org.eclipsefoundation.git.eca.test.namespaces.SchemaNamespaceHelper;
import org.eclipsefoundation.testing.helpers.AuthHelper;
import org.eclipsefoundation.testing.helpers.TestCaseHelper;
import org.eclipsefoundation.testing.templates.RestAssuredTemplates;
import org.eclipsefoundation.testing.templates.RestAssuredTemplates.EndpointTestCase;
import org.junit.jupiter.api.Test;
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.ConfigMetadata;
import io.quarkus.test.security.oidc.OidcSecurity;
import io.restassured.http.ContentType;
@QuarkusTest
......@@ -31,104 +36,140 @@ class ReportsResourceTest {
public static final String REPORTS_PROJECTS_RANGE_URL = REPORTS_PROJECTS_URL + "?since={start}&until={end}";
public static final EndpointTestCase GET_REPORT_SUCCESS_CASE = TestCaseHelper
.buildSuccessCase(REPORTS_PROJECTS_URL, new String[] {},
.buildSuccessCase(REPORTS_PROJECTS_URL, new String[] {}, SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_ACTIVE_SUCCESS_CASE = TestCaseHelper
.buildSuccessCase(REPORTS_PROJECTS_STATUS_URL, new String[] { GitEcaParameterNames.STATUS_ACTIVE.getName() },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_ACTIVE_SUCCESS_CASE = TestCaseHelper.buildSuccessCase(
REPORTS_PROJECTS_STATUS_URL, new String[] { GitEcaParameterNames.STATUS_ACTIVE.getName() },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_DELETED_SUCCESS_CASE = TestCaseHelper
.buildSuccessCase(REPORTS_PROJECTS_STATUS_URL, new String[] { GitEcaParameterNames.STATUS_DELETED.getName() },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_DELETED_SUCCESS_CASE = TestCaseHelper.buildSuccessCase(
REPORTS_PROJECTS_STATUS_URL, new String[] { GitEcaParameterNames.STATUS_DELETED.getName() },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_SINCE_SUCCESS_CASE = TestCaseHelper
.buildSuccessCase(REPORTS_PROJECTS_SINCE_URL, new String[] { "2022-11-11" },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_SINCE_SUCCESS_CASE = TestCaseHelper.buildSuccessCase(
REPORTS_PROJECTS_SINCE_URL, new String[] { "2022-11-11" },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_UNTIL_SUCCESS_CASE = TestCaseHelper
.buildSuccessCase(REPORTS_PROJECTS_UNTIL_URL, new String[] { "2022-11-15" },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_UNTIL_SUCCESS_CASE = TestCaseHelper.buildSuccessCase(
REPORTS_PROJECTS_UNTIL_URL, new String[] { "2022-11-15" },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_RANGE_SUCCESS_CASE = TestCaseHelper
.buildSuccessCase(REPORTS_PROJECTS_RANGE_URL, new String[] { "2022-11-15", "2022-11-15" },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_RANGE_SUCCESS_CASE = TestCaseHelper.buildSuccessCase(
REPORTS_PROJECTS_RANGE_URL, new String[] { "2022-11-15", "2022-11-15" },
SchemaNamespaceHelper.PRIVATE_PROJECT_EVENTS_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_BAD_STATUS_CASE = TestCaseHelper
.buildBadRequestCase(REPORTS_PROJECTS_STATUS_URL, new String[] { "nope" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_BAD_STATUS_CASE = TestCaseHelper.buildBadRequestCase(
REPORTS_PROJECTS_STATUS_URL, new String[] { "nope" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_BAD_UNTIL_CASE = TestCaseHelper
.buildBadRequestCase(REPORTS_PROJECTS_UNTIL_URL, new String[] { "nope" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_BAD_UNTIL_CASE = TestCaseHelper.buildBadRequestCase(
REPORTS_PROJECTS_UNTIL_URL, new String[] { "nope" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_BAD_SINCE_CASE = TestCaseHelper
.buildBadRequestCase(REPORTS_PROJECTS_SINCE_URL, new String[] { "nope" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_BAD_SINCE_CASE = TestCaseHelper.buildBadRequestCase(
REPORTS_PROJECTS_SINCE_URL, new String[] { "nope" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH);
public static final EndpointTestCase GET_REPORT_UNAUTHORIZED_CASE = EndpointTestCase
.builder()
.setPath(REPORTS_PROJECTS_URL)
.setStatusCode(401)
.build();
/*
* GET /reports/webhooks/gitlab/system
* GET /reports/gitlab/private-projects
*/
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
@OidcSecurity(claims = { @Claim(key = AuthHelper.EMAIL_CLAIM_KEY, value = AuthHelper.EMAIL_CLAIM_VALUE) }, userinfo = {}, config = {
@ConfigMetadata(key = AuthHelper.ISSUER_FIELD_KEY, value = AuthHelper.ISSUER_FIELD_VALUE) })
void getPrivProjReport_success() {
RestAssuredTemplates.testGet(GET_REPORT_SUCCESS_CASE);
}
@Test
@TestSecurity(user = "jwick", roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReport_success_multipleValidUsers() {
RestAssuredTemplates.testGet(GET_REPORT_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReport_success_validateSchema() {
RestAssuredTemplates.testGet_validateSchema(GET_REPORT_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReport_success_validateResponseFormat() {
RestAssuredTemplates.testGet_validateResponseFormat(GET_REPORT_SUCCESS_CASE);
}
@Test
void getPrivProjReport_failure_unauthenticated() {
// Quarkus will apply 401 for failed authentication
RestAssuredTemplates.testGet(GET_REPORT_UNAUTHORIZED_CASE);
}
@Test
@TestSecurity(user = "badActorz", roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReport_failure_unauthorizedUser() {
RestAssuredTemplates.testGet(GET_REPORT_UNAUTHORIZED_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReport_failure_invalidRequestFormat() {
RestAssuredTemplates.testGet(
TestCaseHelper.buildInvalidFormatCase(REPORTS_PROJECTS_URL, new String[] {}, ContentType.TEXT));
RestAssuredTemplates.testGet(TestCaseHelper.buildInvalidFormatCase(REPORTS_PROJECTS_URL, new String[] {}, ContentType.TEXT));
}
/*
* GET /reports/webhooks/gitlab/system?status=active
* GET /reports/gitlab/private-projects?status=active
*/
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportActive_success() {
RestAssuredTemplates.testGet(GET_REPORT_ACTIVE_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportActive_success_validateSchema() {
RestAssuredTemplates.testGet_validateSchema(GET_REPORT_ACTIVE_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportActive_success_validateResponseFormat() {
RestAssuredTemplates.testGet_validateResponseFormat(GET_REPORT_ACTIVE_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportStatus_failure_invalidStatus() {
RestAssuredTemplates.testGet(GET_REPORT_BAD_STATUS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportStatus_failure_invalidStatus_validate_schema() {
RestAssuredTemplates.testGet_validateSchema(GET_REPORT_BAD_STATUS_CASE);
}
/*
* GET /reports/webhooks/gitlab/system?status=deleted
* GET /reports/gitlab/private-projects?status=deleted
*/
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportDeleted_success() {
RestAssuredTemplates.testGet(GET_REPORT_DELETED_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportDeleted_success_validateSchema() {
RestAssuredTemplates.testGet_validateSchema(GET_REPORT_DELETED_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportDeleted_success_validateResponseFormat() {
RestAssuredTemplates.testGet_validateResponseFormat(GET_REPORT_DELETED_SUCCESS_CASE);
}
......@@ -137,54 +178,64 @@ class ReportsResourceTest {
* GET /reports/webhooks/gitlab/system?since={date}
*/
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportSince_success() {
RestAssuredTemplates.testGet(GET_REPORT_SINCE_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportSince_success_validateSchema() {
RestAssuredTemplates.testGet_validateSchema(GET_REPORT_SINCE_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportSince_success_validateResponseFormat() {
RestAssuredTemplates.testGet_validateResponseFormat(GET_REPORT_SINCE_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportSince_failure_invalidStatus() {
RestAssuredTemplates.testGet(GET_REPORT_BAD_SINCE_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportSince_failure_invalidStatus_validate_schema() {
RestAssuredTemplates.testGet_validateSchema(GET_REPORT_BAD_SINCE_CASE);
}
/*
* GET /reports/webhooks/gitlab/system?until={date}
* GET /reports/gitlab/private-projects?until={date}
*/
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportUntil_success() {
RestAssuredTemplates.testGet(GET_REPORT_UNTIL_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportUntil_success_validateSchema() {
RestAssuredTemplates.testGet_validateSchema(GET_REPORT_UNTIL_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportUntil_success_validateResponseFormat() {
RestAssuredTemplates.testGet_validateResponseFormat(GET_REPORT_UNTIL_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportUntil_failure_invalidStatus() {
RestAssuredTemplates.testGet(GET_REPORT_BAD_UNTIL_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportSUntil_failure_invalidStatus_validate_schema() {
RestAssuredTemplates.testGet_validateSchema(GET_REPORT_BAD_UNTIL_CASE);
}
......@@ -193,16 +244,19 @@ class ReportsResourceTest {
* GET /reports/webhooks/gitlab/system?since={date}&until={date}
*/
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportRange_success() {
RestAssuredTemplates.testGet(GET_REPORT_RANGE_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportRange_success_validateSchema() {
RestAssuredTemplates.testGet_validateSchema(GET_REPORT_RANGE_SUCCESS_CASE);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE)
void getPrivProjReportRange_success_validateResponseFormat() {
RestAssuredTemplates.testGet_validateResponseFormat(GET_REPORT_RANGE_SUCCESS_CASE);
}
......
......@@ -4,6 +4,8 @@ org.eclipsefoundation.git.eca.api.BotsAPI/mp-rest/url=https://api.eclipse.org
eclipse.noreply.email-patterns=@users.noreply.github.com\$
eclipse.mail.allowlist=noreply@github.com
eclipse.reports.allowed-users=opearson,jwick
eclipse.gitlab.access-token=token_val
## DATASOURCE CONFIG
quarkus.datasource.db-kind=h2
......@@ -17,12 +19,12 @@ quarkus.flyway.migrate-at-start=true
## Expect to be mounted to '/git' to match current URL spec
quarkus.http.root-path=/git
quarkus.http.port=8080
## OIDC Connection/Authentication Info
quarkus.oauth2.enabled=false
quarkus.oidc.enabled=false
quarkus.keycloak.devservices.enabled=false
quarkus.oidc-client.enabled=false
quarkus.http.port=8080
quarkus.oidc.client-id=quarkus-service-app
quarkus.oidc.application-type=service
eclipse.gitlab.access-token=token_val
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