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

feat(oidc): Switch API from Drupal OAuth to use Keycloak in its place

As part of the migration away from drupal auth, openvsx is one of the 3
remaining services that use the oauth server in the API stack. With only
1 main consumer, this should be a relatively simple switch over.
parent dc7a830c
No related branches found
No related tags found
1 merge request!34feat(oidc): Switch API from Drupal OAuth to use Keycloak in its place
......@@ -87,7 +87,7 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-oidc-filter</artifactId>
<artifactId>quarkus-oidc-client</artifactId>
</dependency>
<!-- Testing dependencies only -->
......@@ -97,6 +97,21 @@
<version>${eclipse-api-version}</version>
<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.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
......@@ -182,4 +197,4 @@
</properties>
</profile>
</profiles>
</project>
\ No newline at end of file
</project>
......@@ -12,24 +12,43 @@
package org.eclipsefoundation.openvsx.resources;
import java.util.Arrays;
import java.util.List;
import org.eclipsefoundation.efservices.api.models.EfUser;
import org.eclipsefoundation.efservices.models.AuthenticatedRequestWrapper;
import org.eclipsefoundation.efservices.services.ProfileService;
import org.eclipsefoundation.http.exception.ApplicationException;
import jakarta.inject.Inject;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
@Authenticated
@Path("profile")
public class ProfileResource {
@Inject
AuthenticatedRequestWrapper userProfile;
private final ProfileService profile;
private final SecurityIdentity ident;
/**
* Default constructor, taking in the required services to function.
*
* @param profile service that retrieves EF user profiles by username
* @param ident logged in OIDC user
*/
public ProfileResource(ProfileService profile, SecurityIdentity ident) {
this.profile = profile;
this.ident = ident;
}
@GET
public List<EfUser> getProfileInfo() {
public Response getProfileInfo() {
// Returns the public profile data tied to the current user
return Arrays.asList(userProfile.getCurrentUser().getPublicProfile());
return Response
.ok(Arrays
.asList(profile
.fetchUserByUsername(ident.getPrincipal().getName(), true)
.orElseThrow(() -> new ApplicationException("No user profile found for logged in user", 500))
.getPublicProfile()))
.build();
}
}
......@@ -15,9 +15,9 @@ import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.eclipsefoundation.efservices.api.models.EfUser;
import org.eclipsefoundation.efservices.models.AuthenticatedRequestWrapper;
import org.eclipsefoundation.efservices.services.ProfileService;
import org.eclipsefoundation.foundationdb.client.runtime.model.people.PeopleDocumentData;
import org.eclipsefoundation.http.model.WebError;
import org.eclipsefoundation.http.exception.ApplicationException;
import org.eclipsefoundation.openvsx.config.PublisherAgreementConfig;
import org.eclipsefoundation.openvsx.models.AgreementSigningRequest;
import org.eclipsefoundation.openvsx.models.PublisherAgreementData;
......@@ -25,7 +25,8 @@ import org.eclipsefoundation.openvsx.services.FoundationOperationService;
import org.eclipsefoundation.openvsx.services.PublisherAgreementService;
import org.eclipsefoundation.utils.exception.FinalForbiddenException;
import jakarta.inject.Inject;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
......@@ -37,51 +38,70 @@ import jakarta.ws.rs.ServerErrorException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
/**
* Resource containing calls for retrieving and signing OpenVSX publisher agreements.
*/
@Authenticated
@Path("publisher_agreement")
public class PublisherAgreementResource {
private static final String NOT_FOUND_MSG_FORMAT = "Unable to find agreement for user: %s";
@Inject
PublisherAgreementConfig config;
@Inject
AuthenticatedRequestWrapper userProfile;
@Inject
PublisherAgreementService agreementService;
@Inject
FoundationOperationService foundationService;
private final PublisherAgreementConfig config;
private final PublisherAgreementService agreementService;
private final FoundationOperationService foundationService;
private final ProfileService profile;
private final SecurityIdentity ident;
public PublisherAgreementResource(PublisherAgreementConfig config, PublisherAgreementService agreementService,
FoundationOperationService foundationService, ProfileService profile, SecurityIdentity ident) {
this.config = config;
this.agreementService = agreementService;
this.foundationService = foundationService;
this.profile = profile;
this.ident = ident;
}
/**
* Retrieve the currently active publisher agreement if one exists.
*
* @return the publisher agreement if it exists
* @throws NotFoundException when there is no agreement in place for the current user
*/
@GET
public Response getAgreement() {
// Uses currently logged in user. Only an onwer can fetch their agreement
String username = userProfile.getCurrentUser().name();
// Fetch agreement for user
Optional<PublisherAgreementData> result = agreementService.getPublisherAgreementByUsername(username);
if (result.isEmpty()) {
throw new NotFoundException(String.format(NOT_FOUND_MSG_FORMAT, username));
}
return Response.ok(result).build();
public PublisherAgreementData getAgreement() {
// Uses currently logged in user. Only an owner can fetch their agreement
String username = ident.getPrincipal().getName();
// Fetch agreement for user, throwing a 404 exception if there is no active agreement
return agreementService
.getPublisherAgreementByUsername(username)
.orElseThrow(() -> new NotFoundException(String.format(NOT_FOUND_MSG_FORMAT, username)));
}
/**
* Sign a new OpenVSX publisher agreement for the currently logged in user. A new agreement will only be signed and persisted if there
* isn't already an active agreement for the user.
*
* @param body the request containing the version and GitHub handle of the user
* @return the newly signed agreement.
* @throws BadRequestException if the passed agreement parameters do not match the configured allowed version, if the logged in user
* doesn't match the passed GitHub handle, or if the agreement cannot be created.
* @throws ApplicationException if the user in question already has an active agreement
* @throws ServerErrorException if a foundation database personal record cannot be found or created for the active user
*/
@POST
public Response createAgreement(AgreementSigningRequest body) {
public PublisherAgreementData createAgreement(AgreementSigningRequest body) {
// Uses currently logged in user. Only an owner can create their agreement
EfUser user = userProfile.getCurrentUser();
EfUser user = getLoggedInUser();
// Check if body format is correct
validateSigningRequest(body);
// Conflict if already signed agreement that isn't expired
if (agreementService.getPublisherAgreementByUsername(user.name()).isPresent()) {
return new WebError(Status.CONFLICT,
"The request could not be completed due to a conflict with the current state of the resource.").asResponse();
throw new ApplicationException("The request could not be completed due to a conflict with the current state of the resource.",
Status.CONFLICT.getStatusCode());
}
// Create user in fdndb if they don't exist
......@@ -89,29 +109,40 @@ public class PublisherAgreementResource {
throw new ServerErrorException("Internal Server Error", Status.INTERNAL_SERVER_ERROR);
}
// Attempt to persist the agreement data. Returning the result
Optional<PublisherAgreementData> result = agreementService.createPublisherAgreement(user);
if (result.isEmpty()) {
throw new BadRequestException("Unable to create Publisher Agreement with current request parameters");
}
return Response.ok(result.get()).build();
// Attempt to persist the agreement data, returning the new agreement on success
return agreementService
.createPublisherAgreement(user)
.orElseThrow(() -> new BadRequestException("Unable to create Publisher Agreement with current request parameters"));
}
/**
* Retrieve the active publisher agreement for the listed user.
*
* @return the publisher agreement if it exists
* @throws NotFoundException when there is no agreement in place for the indicated user
* @throws FinalForbiddenException if the username doesn't match the logged in user, and the logged in user isn't an admin
*/
@GET
@Path("{efUsername}")
public Response getAgreementForUser(@PathParam("efUsername") String username) {
public PublisherAgreementData getAgreementForUser(@PathParam("efUsername") String username) {
// The owner or admin can retrieve a specific agreement
checkIfAdminOrSelf(username);
Optional<PublisherAgreementData> result = agreementService.getPublisherAgreementByUsername(username);
if (result.isEmpty()) {
throw new NotFoundException(String.format(NOT_FOUND_MSG_FORMAT, username));
}
return Response.ok(result).build();
// Fetch agreement for user, throwing a 404 exception if there is no active agreement
return agreementService
.getPublisherAgreementByUsername(username)
.orElseThrow(() -> new NotFoundException(String.format(NOT_FOUND_MSG_FORMAT, username)));
}
/**
* Revokes an active agreement for the stated user. Can only be triggered by the target user, or by an admin of the service.
*
* @param username the username to target for agreement revocation.
* @return an empty 204 no content response on successful update.
* @throws FinalForbiddenException if the username doesn't match the logged in user, and the logged in user isn't an admin.
* @throws NotFoundException if the user in question doesn't have an active agreement to revoke.
* @throws ServerErrorException if the record cannot be properly revoked.
*/
@DELETE
@Path("{efUsername}")
public Response revokeAgreementForUser(@PathParam("efUsername") String username) {
......@@ -119,18 +150,19 @@ public class PublisherAgreementResource {
// The owner or admin can retrieve a specific agreement
checkIfAdminOrSelf(username);
// check that an agreement exists before revoking
Optional<PeopleDocumentData> fetchResult = foundationService.fetchMostRecentDocument(username);
if (fetchResult.isEmpty()) {
throw new NotFoundException(String.format(NOT_FOUND_MSG_FORMAT, username));
}
String currentUser = userProfile.getCurrentUser().name();
Optional<PeopleDocumentData> updateResult = agreementService.revokePublisherAgreement(fetchResult.get(), currentUser);
// revokes the agreement for the targeted account
Optional<PeopleDocumentData> updateResult = agreementService.revokePublisherAgreement(fetchResult.get(), username);
if (updateResult.isEmpty()) {
throw new ServerErrorException("Internal Server Error", Status.INTERNAL_SERVER_ERROR);
}
// return a 204 response on success
return Response.noContent().build();
}
......@@ -150,7 +182,7 @@ public class PublisherAgreementResource {
}
// Ensure GH handle from current user same as in request body.
if (!StringUtils.equalsIgnoreCase(userProfile.getCurrentUser().githubHandle(), request.githubHandle())) {
if (!StringUtils.equalsIgnoreCase(getLoggedInUser().githubHandle(), request.githubHandle())) {
throw new BadRequestException("The github_handle does not match our records.");
}
}
......@@ -162,12 +194,25 @@ public class PublisherAgreementResource {
* @param urlUsername The username in the request URL
* @return True if current user can access endpoint
*/
void checkIfAdminOrSelf(String urlUsername) {
private void checkIfAdminOrSelf(String urlUsername) {
// Reject request if current user is not in URL and they aren't an admin
EfUser user = userProfile.getCurrentUser();
EfUser user = getLoggedInUser();
if (!urlUsername.equalsIgnoreCase(user.name())
&& config.adminUsers().stream().noneMatch(email -> email.equalsIgnoreCase(user.mail()))) {
throw new FinalForbiddenException(String.format("Access denied to resources for: %s", urlUsername));
}
}
}
\ No newline at end of file
/**
* Using the logged in users username, fetch the associated EF profile.
*
* @return the logged in user account
* @throws ApplicationException if no user account can be mapped to the user account. This is typically when there is something wrong
* with the service.
*/
private EfUser getLoggedInUser() {
return profile
.fetchUserByUsername(ident.getPrincipal().getName(), true)
.orElseThrow(() -> new NotFoundException("No user profile found for logged in user"));
}
}
quarkus.http.root-path=/openvsx
quarkus.log.level=INFO
quarkus.oidc.enabled=false
fdndb-api/mp-rest/url=http://foundationdb:8095
%dev.fdndb-api/mp-rest/url=http://localhost:10112
## Bypass the cache for user objects, as we want to always have latest
eclipse.cache.cache-bypass-classes=org.eclipsefoundation.efservices.api.models.EfUser
eclipse.security.oauth2.filter.enabled=true
eclipse.security.oauth2.filter.always-on.enabled=true
eclipse.security.oauth2.filter.enabled=false
quarkus.log.file.enable=false
quarkus.micrometer.enabled=true
\ No newline at end of file
quarkus.micrometer.enabled=true
......@@ -15,64 +15,58 @@ import java.util.Map;
import java.util.Optional;
import org.eclipsefoundation.openvsx.test.helpers.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.Test;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
@QuarkusTest
class ProfileResourceTest {
public static final String BASE_URL = "profile";
public static final Optional<Map<String, Object>> userCreds = Optional.of(Map.of("Authorization", "Bearer token2"));
public static final Optional<Map<String, Object>> noUserCreds = Optional.of(Map.of("Authorization", "Bearer token6"));
public static final Optional<Map<String, Object>> invalidCreds = Optional.of(Map.of("Authorization", "Bearer token1"));
public static final String FAKEUSER_PROFILE = "fakeuser";
public static final EndpointTestCase GET_CURRENT_SUCCESS = TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.EF_USERS_SCHEMA_PATH)
.setHeaderParams(userCreds)
.build();
/*
* GET CURRENT USER
*/
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGetProfile_success() {
EndpointTestBuilder.from(GET_CURRENT_SUCCESS).run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGetProfile_success_validateResponseFormat() {
EndpointTestBuilder.from(GET_CURRENT_SUCCESS).andCheckFormat().run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGetProfile_success_validateSchema() {
EndpointTestBuilder.from(GET_CURRENT_SUCCESS).andCheckSchema().run();
}
@Test
@TestSecurity(user = "")
void testGetProfile_failure_anonymousToken() {
EndpointTestBuilder
.from(TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, null)
.setStatusCode(403)
.setHeaderParams(noUserCreds)
.build())
.run();
EndpointTestBuilder.from(TestCaseHelper.prepareTestCase(BASE_URL, new String[] {}, null).setStatusCode(401).build()).run();
}
@Test
void testGetProfile_failure_invalidCreds() {
void testGetProfile_failure_noValidToken() {
EndpointTestBuilder
.from(TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, null)
.setStatusCode(403)
.setHeaderParams(invalidCreds)
.setStatusCode(401)
.setHeaderParams(Optional.of(Map.of("Authorization", "Bearer token1")))
.build())
.run();
}
......
......@@ -16,6 +16,7 @@ import java.util.Optional;
import org.eclipsefoundation.openvsx.models.AgreementSigningRequest;
import org.eclipsefoundation.openvsx.test.helpers.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;
......@@ -25,6 +26,7 @@ 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.restassured.http.ContentType;
import jakarta.inject.Inject;
......@@ -33,77 +35,66 @@ class PublisherAgreementResourceTest {
public static final String BASE_URL = "publisher_agreement";
public static final String USER_URL = BASE_URL + "/{efusername}";
public static final Optional<Map<String, Object>> userCreds = Optional.of(Map.of("Authorization", "Bearer token2"));
public static final Optional<Map<String, Object>> userNoDocCreds = Optional.of(Map.of("Authorization", "Bearer token5"));
public static final String FAKEUSER_PROFILE = "fakeuser";
public static final String OTHERUSER_PROFILE = "otheruser";
public static final String NODOC_PROFILE = "nodoc";
public static final Optional<Map<String, Object>> invalidCreds = Optional.of(Map.of("Authorization", "Bearer token1"));
public static final Optional<Map<String, Object>> docCreateCreds = Optional.of(Map.of("Authorization", "Bearer token7"));
public static final EndpointTestCase GET_CURRENT_SUCCESS = TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
.setHeaderParams(userCreds)
.build();
public static final EndpointTestCase GET_CURRENT_NOT_FOUND = TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
.setStatusCode(404)
.setHeaderParams(userNoDocCreds)
.build();
public static final EndpointTestCase BAD_CREDS = TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, null)
.setStatusCode(403)
.setStatusCode(401)
.setHeaderParams(invalidCreds)
.build();
public static final EndpointTestCase POST_CURRENT_CONFLICT = TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, null)
.setStatusCode(409)
.setHeaderParams(userCreds)
.build();
public static final EndpointTestCase POST_CURRENT_INVALID_HANDLE = TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
.setStatusCode(400)
.setHeaderParams(userCreds)
.build();
public static final EndpointTestCase GET_USER_SUCCESS = TestCaseHelper
.prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
.setHeaderParams(userCreds)
.build();
public static final EndpointTestCase GET_USER_NOT_FOUND = TestCaseHelper
.prepareTestCase(USER_URL, new String[] { "name" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
.setStatusCode(404)
.setHeaderParams(userNoDocCreds)
.build();
public static final EndpointTestCase FOR_USER_BAD_CREDS = TestCaseHelper
.prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
.setStatusCode(403)
.setStatusCode(401)
.setHeaderParams(invalidCreds)
.build();
public static final EndpointTestCase REVOKE_SUCCESS = TestCaseHelper
.prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
.setHeaderParams(userCreds)
.setStatusCode(204)
.build();
public static final EndpointTestCase REVOKE_NO_DOC = TestCaseHelper
.prepareTestCase(USER_URL, new String[] { "name" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
.setStatusCode(404)
.setHeaderParams(userNoDocCreds)
.build();
public static final EndpointTestCase REVOKE_INVALID_USER = TestCaseHelper
.prepareTestCase(USER_URL, new String[] { "other" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
.setStatusCode(403)
.setResponseContentType(ContentType.JSON)
.setHeaderParams(userCreds)
.build();
@Inject
......@@ -113,31 +104,37 @@ class PublisherAgreementResourceTest {
* GET CURRENT USER
*/
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_currentUser_success() {
EndpointTestBuilder.from(GET_CURRENT_SUCCESS).run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_currentUser_success_validateResponseFormat() {
EndpointTestBuilder.from(GET_CURRENT_SUCCESS).andCheckFormat().run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_currentUser_success_validateSchema() {
EndpointTestBuilder.from(GET_CURRENT_SUCCESS).andCheckSchema().run();
}
@Test
@TestSecurity(user = OTHERUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_currentUser_failure_notFound() {
EndpointTestBuilder.from(GET_CURRENT_NOT_FOUND).run();
}
@Test
@TestSecurity(user = OTHERUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_currentUser_failure_notFound_validateResponseFormat() {
EndpointTestBuilder.from(GET_CURRENT_NOT_FOUND).andCheckFormat().run();
}
@Test
@TestSecurity(user = OTHERUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_currentUser_failure_notFound_validateSchema() {
EndpointTestBuilder.from(GET_CURRENT_NOT_FOUND).andCheckSchema().run();
}
......@@ -151,37 +148,42 @@ class PublisherAgreementResourceTest {
* POST CURRENT USER
*/
@Test
@TestSecurity(user = NODOC_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testPost_currentUser_success() {
EndpointTestBuilder
.from(TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
.setHeaderParams(docCreateCreds)
.build())
.doPost(generateSigningSample("nodoc"))
.run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testPost_currentUser_conflict() {
EndpointTestBuilder.from(POST_CURRENT_CONFLICT).doPost(generateSigningSample("fakeuser")).run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testPost_currentUser_conflict_validateResponseFormat() {
EndpointTestBuilder.from(POST_CURRENT_CONFLICT).doPost(generateSigningSample("fakeuser")).andCheckFormat().run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testPost_currentUser_conflict_validateSchema() {
EndpointTestBuilder.from(POST_CURRENT_CONFLICT).doPost(generateSigningSample("fakeuser")).andCheckFormat().run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testPost_currentUser_failure_invalidHandle() {
EndpointTestBuilder.from(POST_CURRENT_INVALID_HANDLE).doPost(generateSigningSample("otheruser")).run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testPost_currentUser_failure_invalidHandle_validateFormat() {
EndpointTestBuilder.from(POST_CURRENT_INVALID_HANDLE).doPost(generateSigningSample("otheruser")).andCheckFormat().run();
}
......@@ -195,31 +197,37 @@ class PublisherAgreementResourceTest {
* GET FOR USER
*/
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_getForUser_success() {
EndpointTestBuilder.from(GET_USER_SUCCESS).run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_geFortUser_success_validateResponseFormat() {
EndpointTestBuilder.from(GET_USER_SUCCESS).andCheckFormat().run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_getForUser_success_validateSchema() {
EndpointTestBuilder.from(GET_USER_SUCCESS).andCheckSchema().run();
}
@Test
@TestSecurity(user = OTHERUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_getForUser_failure_notFound() {
EndpointTestBuilder.from(GET_USER_NOT_FOUND).run();
}
@Test
@TestSecurity(user = OTHERUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_getForUser_failure_notFound_validateResponseFormat() {
EndpointTestBuilder.from(GET_USER_NOT_FOUND).andCheckFormat().run();
}
@Test
@TestSecurity(user = OTHERUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testGet_getForUser_failure_notFound_validateSchema() {
EndpointTestBuilder.from(GET_USER_NOT_FOUND).andCheckSchema().run();
}
......@@ -233,21 +241,25 @@ class PublisherAgreementResourceTest {
* DELETE FOR USER
*/
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testDelete_deleteForUser_success() {
EndpointTestBuilder.from(REVOKE_SUCCESS).doDelete(null).run();
}
@Test
@TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testDelete_deleteForUser_failure_invalidUser() {
EndpointTestBuilder.from(REVOKE_INVALID_USER).doDelete(null).run();
}
@Test
@TestSecurity(user = OTHERUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testDelete_deleteForUser_failure_noDoc() {
EndpointTestBuilder.from(REVOKE_NO_DOC).doDelete(null).run();
}
@Test
@TestSecurity(user = OTHERUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
void testDelete_deleteForUser_failure_noDoc_validateSchema() {
EndpointTestBuilder.from(REVOKE_NO_DOC).doDelete(null).andCheckSchema().run();
}
......
/*********************************************************************
* Copyright (c) 2023 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.openvsx.test.api;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.efservices.api.DrupalOAuthAPI;
import org.eclipsefoundation.efservices.api.models.DrupalOAuthData;
import org.eclipsefoundation.efservices.api.models.DrupalOAuthDataBuilder;
import org.eclipsefoundation.efservices.api.models.DrupalUserInfo;
import org.eclipsefoundation.efservices.helpers.DrupalAuthHelper;
import org.eclipsefoundation.utils.exception.FinalForbiddenException;
import io.quarkus.test.Mock;
import jakarta.enterprise.context.ApplicationScoped;
@Mock
@RestClient
@ApplicationScoped
public class MockDrupalOAuthAPI implements DrupalOAuthAPI {
List<DrupalOAuthData> tokens;
List<DrupalUserInfo> users;
public MockDrupalOAuthAPI() {
tokens = new ArrayList<>();
tokens
.addAll(Arrays
.asList(DrupalOAuthDataBuilder
.builder()
.accessToken("token1")
.clientId("client-id")
.expires(1674111182)
.scope("read write")
.build(),
DrupalOAuthDataBuilder
.builder()
.accessToken("token2")
.clientId("test-id")
.userId("42")
.expires(Instant.now().getEpochSecond() + 20000)
.scope("read write admin")
.build(),
DrupalOAuthDataBuilder
.builder()
.accessToken("token3")
.clientId("test-id")
.expires(1234567890)
.scope("read admin")
.build(),
DrupalOAuthDataBuilder
.builder()
.accessToken("token4")
.clientId("client-id")
.expires(Instant.now().getEpochSecond() + 20000)
.scope("read write")
.build(),
DrupalOAuthDataBuilder
.builder()
.accessToken("token5")
.clientId("test-id")
.userId("333")
.expires(Instant.now().getEpochSecond() + 20000)
.scope("read write admin")
.build(),
DrupalOAuthDataBuilder
.builder()
.accessToken("token6")
.clientId("test-id")
.expires(Instant.now().getEpochSecond() + 20000)
.scope("read write admin")
.build(),
DrupalOAuthDataBuilder
.builder()
.accessToken("token7")
.clientId("test-id")
.userId("444")
.expires(Instant.now().getEpochSecond() + 20000)
.scope("read write admin")
.build()));
users = new ArrayList<>();
users
.addAll(Arrays
.asList(new DrupalUserInfo("42", "fakeuser", "fakeuser"), new DrupalUserInfo("333", "otheruser", "other"),
new DrupalUserInfo("444", "nodoc", "nodoc")));
}
@Override
public DrupalOAuthData getTokenInfo(String token) {
return tokens.stream().filter(t -> t.accessToken().equalsIgnoreCase(token)).findFirst().orElse(null);
}
@Override
public DrupalUserInfo getUserInfoFromToken(String token) {
DrupalOAuthData tokenInfo = getTokenInfo(DrupalAuthHelper.stripBearerToken(token));
if (tokenInfo == null || tokenInfo.userId() == null) {
throw new FinalForbiddenException("The access token " + DrupalAuthHelper.stripBearerToken(token) + " provided is invalid");
}
return users.stream().filter(u -> u.sub().equalsIgnoreCase(tokenInfo.userId())).findFirst().orElse(null);
}
}
\ No newline at end of file
## OIDC Connection/Authentication Info
quarkus.oauth2.enabled=false
quarkus.oidc.enabled=false
quarkus.keycloak.devservices.enabled=false
quarkus.oidc-client.enabled=false
eclipse.security.oauth2.filter.enabled=false
eclipse.security.oauth2.filter.valid-client-ids=test-id
eclipse.security.oauth2.filter.valid-scopes=read,write,admin
eclipse.security.oauth2.filter.enabled=true
eclipse.security.oauth2.filter.always-on.enabled=true
quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.client-id=quarkus-service-app
quarkus.oidc.application-type=service
eclipse.openvsx.publisher-agreement.doc-id=sampleId
eclipse.openvsx.publisher-agreement.doc-version=1
eclipse.openvsx.publisher-agreement.admin-users=admin@email.com
eclipse.security.oauth2.token-generation.client-secret=sample
eclipse.security.oauth2.token-generation.client-id=sample
eclipse.security.oauth2.token-generation.scope=sample
quarkus.jacoco.includes=**/openvsx/**/*
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