From 1a95ce6755b69f60a0488b097d7bb477efa8d581 Mon Sep 17 00:00:00 2001
From: Martin Lowe <martin.lowe@eclipse-foundation.org>
Date: Fri, 14 Mar 2025 14:25:05 -0400
Subject: [PATCH 1/2] 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.
---
 pom.xml                                       |  19 ++-
 .../openvsx/resources/ProfileResource.java    |  35 +++-
 .../resources/PublisherAgreementResource.java | 149 ++++++++++++------
 src/main/resources/application.properties     |   7 +-
 .../resources/ProfileResourceTest.java        |  28 ++--
 .../PublisherAgreementResourceTest.java       |  46 ++++--
 .../openvsx/test/api/MockDrupalOAuthAPI.java  | 116 --------------
 src/test/resources/application.properties     |  13 +-
 8 files changed, 187 insertions(+), 226 deletions(-)
 delete mode 100644 src/test/java/org/eclipsefoundation/openvsx/test/api/MockDrupalOAuthAPI.java

diff --git a/pom.xml b/pom.xml
index 97e00cc..29030b8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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>
diff --git a/src/main/java/org/eclipsefoundation/openvsx/resources/ProfileResource.java b/src/main/java/org/eclipsefoundation/openvsx/resources/ProfileResource.java
index eb424ae..659af96 100644
--- a/src/main/java/org/eclipsefoundation/openvsx/resources/ProfileResource.java
+++ b/src/main/java/org/eclipsefoundation/openvsx/resources/ProfileResource.java
@@ -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();
     }
 }
diff --git a/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java b/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java
index cfff5a7..1c93e27 100644
--- a/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java
+++ b/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java
@@ -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"));
+    }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 04dc27f..d78ed36 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,16 +1,13 @@
 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
diff --git a/src/test/java/org/eclipsefoundation/openvsx/resources/ProfileResourceTest.java b/src/test/java/org/eclipsefoundation/openvsx/resources/ProfileResourceTest.java
index 6124c93..6f13fd6 100644
--- a/src/test/java/org/eclipsefoundation/openvsx/resources/ProfileResourceTest.java
+++ b/src/test/java/org/eclipsefoundation/openvsx/resources/ProfileResourceTest.java
@@ -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();
     }
diff --git a/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java b/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java
index 3bac452..5f8d34f 100644
--- a/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java
+++ b/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java
@@ -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();
     }
diff --git a/src/test/java/org/eclipsefoundation/openvsx/test/api/MockDrupalOAuthAPI.java b/src/test/java/org/eclipsefoundation/openvsx/test/api/MockDrupalOAuthAPI.java
deleted file mode 100644
index e845c5d..0000000
--- a/src/test/java/org/eclipsefoundation/openvsx/test/api/MockDrupalOAuthAPI.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*********************************************************************
-* 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
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index 9397921..6389235 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -1,20 +1,15 @@
 ## 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/**/*
-- 
GitLab


From 0c96c2cf235549f427c5793d3f68490fa0b3567b Mon Sep 17 00:00:00 2001
From: Martin Lowe <martin.lowe@eclipse-foundation.org>
Date: Wed, 4 Jun 2025 11:15:41 -0400
Subject: [PATCH 2/2] fix: Add required role to the endpoint calls

---
 .../openvsx/namespace/OpenVSXParameters.java  |  19 +
 .../openvsx/resources/ProfileResource.java    |   5 +-
 .../resources/PublisherAgreementResource.java |   5 +-
 .../resources/ProfileResourceTest.java        |  20 +-
 .../PublisherAgreementResourceTest.java       | 509 +++++++++---------
 5 files changed, 306 insertions(+), 252 deletions(-)
 create mode 100644 src/main/java/org/eclipsefoundation/openvsx/namespace/OpenVSXParameters.java

diff --git a/src/main/java/org/eclipsefoundation/openvsx/namespace/OpenVSXParameters.java b/src/main/java/org/eclipsefoundation/openvsx/namespace/OpenVSXParameters.java
new file mode 100644
index 0000000..5909edf
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/openvsx/namespace/OpenVSXParameters.java
@@ -0,0 +1,19 @@
+/*********************************************************************
+* Copyright (c) 2025 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/
+*
+* SPDX-License-Identifier: EPL-2.0
+**********************************************************************/
+package org.eclipsefoundation.openvsx.namespace;
+
+/**
+ * Shared parameters used in the operation of the API.
+ */
+public class OpenVSXParameters {
+  public static final String DEFAULT_ACCESS_ROLE = "openvsx_publisher_agreement";
+
+  private OpenVSXParameters() {}
+}
diff --git a/src/main/java/org/eclipsefoundation/openvsx/resources/ProfileResource.java b/src/main/java/org/eclipsefoundation/openvsx/resources/ProfileResource.java
index 659af96..cf53436 100644
--- a/src/main/java/org/eclipsefoundation/openvsx/resources/ProfileResource.java
+++ b/src/main/java/org/eclipsefoundation/openvsx/resources/ProfileResource.java
@@ -15,15 +15,16 @@ import java.util.Arrays;
 
 import org.eclipsefoundation.efservices.services.ProfileService;
 import org.eclipsefoundation.http.exception.ApplicationException;
+import org.eclipsefoundation.openvsx.namespace.OpenVSXParameters;
 
-import io.quarkus.security.Authenticated;
 import io.quarkus.security.identity.SecurityIdentity;
+import jakarta.annotation.security.RolesAllowed;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.core.Response;
 
-@Authenticated
 @Path("profile")
+@RolesAllowed(OpenVSXParameters.DEFAULT_ACCESS_ROLE)
 public class ProfileResource {
 
     private final ProfileService profile;
diff --git a/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java b/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java
index 1c93e27..8fb3582 100644
--- a/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java
+++ b/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java
@@ -21,12 +21,13 @@ import org.eclipsefoundation.http.exception.ApplicationException;
 import org.eclipsefoundation.openvsx.config.PublisherAgreementConfig;
 import org.eclipsefoundation.openvsx.models.AgreementSigningRequest;
 import org.eclipsefoundation.openvsx.models.PublisherAgreementData;
+import org.eclipsefoundation.openvsx.namespace.OpenVSXParameters;
 import org.eclipsefoundation.openvsx.services.FoundationOperationService;
 import org.eclipsefoundation.openvsx.services.PublisherAgreementService;
 import org.eclipsefoundation.utils.exception.FinalForbiddenException;
 
-import io.quarkus.security.Authenticated;
 import io.quarkus.security.identity.SecurityIdentity;
+import jakarta.annotation.security.RolesAllowed;
 import jakarta.ws.rs.BadRequestException;
 import jakarta.ws.rs.DELETE;
 import jakarta.ws.rs.GET;
@@ -41,8 +42,8 @@ import jakarta.ws.rs.core.Response.Status;
 /**
  * Resource containing calls for retrieving and signing OpenVSX publisher agreements.
  */
-@Authenticated
 @Path("publisher_agreement")
+@RolesAllowed(OpenVSXParameters.DEFAULT_ACCESS_ROLE)
 public class PublisherAgreementResource {
 
     private static final String NOT_FOUND_MSG_FORMAT = "Unable to find agreement for user: %s";
diff --git a/src/test/java/org/eclipsefoundation/openvsx/resources/ProfileResourceTest.java b/src/test/java/org/eclipsefoundation/openvsx/resources/ProfileResourceTest.java
index 6f13fd6..58fe3a1 100644
--- a/src/test/java/org/eclipsefoundation/openvsx/resources/ProfileResourceTest.java
+++ b/src/test/java/org/eclipsefoundation/openvsx/resources/ProfileResourceTest.java
@@ -14,8 +14,8 @@ package org.eclipsefoundation.openvsx.resources;
 import java.util.Map;
 import java.util.Optional;
 
+import org.eclipsefoundation.openvsx.namespace.OpenVSXParameters;
 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;
@@ -37,19 +37,19 @@ class ProfileResourceTest {
      * GET CURRENT USER
      */
     @Test
-    @TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
+    @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
     void testGetProfile_success() {
         EndpointTestBuilder.from(GET_CURRENT_SUCCESS).run();
     }
 
     @Test
-    @TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
+    @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
     void testGetProfile_success_validateResponseFormat() {
         EndpointTestBuilder.from(GET_CURRENT_SUCCESS).andCheckFormat().run();
     }
 
     @Test
-    @TestSecurity(user = FAKEUSER_PROFILE, roles = AuthHelper.DEFAULT_ROLE)
+    @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
     void testGetProfile_success_validateSchema() {
         EndpointTestBuilder.from(GET_CURRENT_SUCCESS).andCheckSchema().run();
     }
@@ -70,4 +70,16 @@ class ProfileResourceTest {
                         .build())
                 .run();
     }
+    
+    @Test
+    @TestSecurity(user = FAKEUSER_PROFILE, roles = "user")
+    void testGetProfile_failure_noValidRole() {
+        EndpointTestBuilder
+                .from(TestCaseHelper
+                        .prepareTestCase(BASE_URL, new String[] {}, null)
+                        .setStatusCode(403)
+                        .setHeaderParams(Optional.of(Map.of("Authorization", "Bearer token1")))
+                        .build())
+                .run();
+    }
 }
diff --git a/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java b/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java
index 5f8d34f..50259db 100644
--- a/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java
+++ b/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java
@@ -15,8 +15,8 @@ import java.util.Map;
 import java.util.Optional;
 
 import org.eclipsefoundation.openvsx.models.AgreementSigningRequest;
+import org.eclipsefoundation.openvsx.namespace.OpenVSXParameters;
 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;
@@ -32,248 +32,269 @@ import jakarta.inject.Inject;
 
 @QuarkusTest
 class PublisherAgreementResourceTest {
-    public static final String BASE_URL = "publisher_agreement";
-    public static final String USER_URL = BASE_URL + "/{efusername}";
-
-    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 EndpointTestCase GET_CURRENT_SUCCESS = TestCaseHelper
-            .prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
-            .build();
-
-    public static final EndpointTestCase GET_CURRENT_NOT_FOUND = TestCaseHelper
-            .prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
-            .setStatusCode(404)
-            .build();
-
-    public static final EndpointTestCase BAD_CREDS = TestCaseHelper
-            .prepareTestCase(BASE_URL, new String[] {}, null)
-            .setStatusCode(401)
-            .setHeaderParams(invalidCreds)
-            .build();
-
-    public static final EndpointTestCase POST_CURRENT_CONFLICT = TestCaseHelper
-            .prepareTestCase(BASE_URL, new String[] {}, null)
-            .setStatusCode(409)
-            .build();
-
-    public static final EndpointTestCase POST_CURRENT_INVALID_HANDLE = TestCaseHelper
-            .prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
-            .setStatusCode(400)
-            .build();
-
-    public static final EndpointTestCase GET_USER_SUCCESS = TestCaseHelper
-            .prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
-            .build();
-
-    public static final EndpointTestCase GET_USER_NOT_FOUND = TestCaseHelper
-            .prepareTestCase(USER_URL, new String[] { "name" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
-            .setStatusCode(404)
-            .build();
-
-    public static final EndpointTestCase FOR_USER_BAD_CREDS = TestCaseHelper
-            .prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
-            .setStatusCode(401)
-            .setHeaderParams(invalidCreds)
-            .build();
-
-    public static final EndpointTestCase REVOKE_SUCCESS = TestCaseHelper
-            .prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
-            .setStatusCode(204)
-            .build();
-
-    public static final EndpointTestCase REVOKE_NO_DOC = TestCaseHelper
-            .prepareTestCase(USER_URL, new String[] { "name" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
-            .setStatusCode(404)
-            .build();
-
-    public static final EndpointTestCase REVOKE_INVALID_USER = TestCaseHelper
-            .prepareTestCase(USER_URL, new String[] { "other" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
-            .setStatusCode(403)
-            .setResponseContentType(ContentType.JSON)
-            .build();
-
-    @Inject
-    ObjectMapper mapper;
-
-    /*
-     * 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();
-    }
-
-    @Test
-    void testGet_currentUser_failure_badCreds() {
-        EndpointTestBuilder.from(BAD_CREDS).run();
-    }
-
-    /*
-     * 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)
-                        .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();
-    }
-
-    @Test
-    void testPost_currentUser_failure_badCreds() {
-        EndpointTestBuilder.from(BAD_CREDS).doPost(generateSigningSample("fakeuser")).run();
-    }
-
-    /*
-     * 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();
-    }
-
-    @Test
-    void testGet_getForUser_failure_badCreds() {
-        EndpointTestBuilder.from(FOR_USER_BAD_CREDS).run();
-    }
-
-    /*
-     * 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();
-    }
-
-    @Test
-    void testDelete_deleteForUser_failure_badCreds() {
-        EndpointTestBuilder.from(FOR_USER_BAD_CREDS).run();
-    }
-
-    private String generateSigningSample(String ghHandle) {
-        try {
-            return mapper.writeValueAsString(new AgreementSigningRequest("1", ghHandle));
-        } catch (JsonProcessingException e) {
-            throw new RuntimeException(e);
-        }
+  public static final String BASE_URL = "publisher_agreement";
+  public static final String USER_URL = BASE_URL + "/{efusername}";
+
+  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 EndpointTestCase GET_CURRENT_SUCCESS = TestCaseHelper
+      .prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
+      .build();
+
+  public static final EndpointTestCase GET_CURRENT_NOT_FOUND = TestCaseHelper
+      .prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
+      .setStatusCode(404)
+      .build();
+
+  public static final EndpointTestCase BAD_CREDS = TestCaseHelper
+      .prepareTestCase(BASE_URL, new String[] {}, null)
+      .setStatusCode(401)
+      .setHeaderParams(invalidCreds)
+      .build();
+
+  public static final EndpointTestCase POST_CURRENT_CONFLICT = TestCaseHelper
+      .prepareTestCase(BASE_URL, new String[] {}, null)
+      .setStatusCode(409)
+      .build();
+
+  public static final EndpointTestCase POST_CURRENT_INVALID_HANDLE = TestCaseHelper
+      .prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
+      .setStatusCode(400)
+      .build();
+
+  public static final EndpointTestCase GET_USER_SUCCESS = TestCaseHelper
+      .prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
+      .build();
+
+  public static final EndpointTestCase GET_USER_NOT_FOUND = TestCaseHelper
+      .prepareTestCase(USER_URL, new String[] { "name" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
+      .setStatusCode(404)
+      .build();
+
+  public static final EndpointTestCase FOR_USER_BAD_CREDS = TestCaseHelper
+      .prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
+      .setStatusCode(401)
+      .setHeaderParams(invalidCreds)
+      .build();
+
+  public static final EndpointTestCase FOR_USER_BAD_ROLE = TestCaseHelper
+      .prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
+      .setStatusCode(403)
+      .setHeaderParams(invalidCreds)
+      .build();
+
+  public static final EndpointTestCase REVOKE_SUCCESS = TestCaseHelper
+      .prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH)
+      .setStatusCode(204)
+      .build();
+
+  public static final EndpointTestCase REVOKE_NO_DOC = TestCaseHelper
+      .prepareTestCase(USER_URL, new String[] { "name" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
+      .setStatusCode(404)
+      .build();
+
+  public static final EndpointTestCase REVOKE_INVALID_USER = TestCaseHelper
+      .prepareTestCase(USER_URL, new String[] { "other" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
+      .setStatusCode(403)
+      .setResponseContentType(ContentType.JSON)
+      .build();
+  public static final EndpointTestCase REVOKE_INVALID_ROLE = TestCaseHelper
+      .prepareTestCase(USER_URL, new String[] { "fakeuser" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH)
+      .setStatusCode(403)
+      .setResponseContentType(ContentType.JSON)
+      .build();
+
+  @Inject
+  ObjectMapper mapper;
+
+  /*
+   * GET CURRENT USER
+   */
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_currentUser_success() {
+    EndpointTestBuilder.from(GET_CURRENT_SUCCESS).run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_currentUser_success_validateResponseFormat() {
+    EndpointTestBuilder.from(GET_CURRENT_SUCCESS).andCheckFormat().run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_currentUser_success_validateSchema() {
+    EndpointTestBuilder.from(GET_CURRENT_SUCCESS).andCheckSchema().run();
+  }
+
+  @Test
+  @TestSecurity(user = OTHERUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_currentUser_failure_notFound() {
+    EndpointTestBuilder.from(GET_CURRENT_NOT_FOUND).run();
+  }
+
+  @Test
+  @TestSecurity(user = OTHERUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_currentUser_failure_notFound_validateResponseFormat() {
+    EndpointTestBuilder.from(GET_CURRENT_NOT_FOUND).andCheckFormat().run();
+  }
+
+  @Test
+  @TestSecurity(user = OTHERUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_currentUser_failure_notFound_validateSchema() {
+    EndpointTestBuilder.from(GET_CURRENT_NOT_FOUND).andCheckSchema().run();
+  }
+
+  @Test
+  void testGet_currentUser_failure_badCreds() {
+    EndpointTestBuilder.from(BAD_CREDS).run();
+  }
+
+  /*
+   * POST CURRENT USER
+   */
+  @Test
+  @TestSecurity(user = NODOC_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testPost_currentUser_success() {
+    EndpointTestBuilder
+        .from(TestCaseHelper.prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.PUBLISHER_AGREEMENT_SCHEMA_PATH).build())
+        .doPost(generateSigningSample("nodoc"))
+        .run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testPost_currentUser_conflict() {
+    EndpointTestBuilder.from(POST_CURRENT_CONFLICT).doPost(generateSigningSample("fakeuser")).run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testPost_currentUser_conflict_validateResponseFormat() {
+    EndpointTestBuilder.from(POST_CURRENT_CONFLICT).doPost(generateSigningSample("fakeuser")).andCheckFormat().run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testPost_currentUser_conflict_validateSchema() {
+    EndpointTestBuilder.from(POST_CURRENT_CONFLICT).doPost(generateSigningSample("fakeuser")).andCheckFormat().run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testPost_currentUser_failure_invalidHandle() {
+    EndpointTestBuilder.from(POST_CURRENT_INVALID_HANDLE).doPost(generateSigningSample("otheruser")).run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testPost_currentUser_failure_invalidHandle_validateFormat() {
+    EndpointTestBuilder.from(POST_CURRENT_INVALID_HANDLE).doPost(generateSigningSample("otheruser")).andCheckFormat().run();
+  }
+
+  @Test
+  void testPost_currentUser_failure_badCreds() {
+    EndpointTestBuilder.from(BAD_CREDS).doPost(generateSigningSample("fakeuser")).run();
+  }
+
+  /*
+   * GET FOR USER
+   */
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_getForUser_success() {
+    EndpointTestBuilder.from(GET_USER_SUCCESS).run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_geFortUser_success_validateResponseFormat() {
+    EndpointTestBuilder.from(GET_USER_SUCCESS).andCheckFormat().run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_getForUser_success_validateSchema() {
+    EndpointTestBuilder.from(GET_USER_SUCCESS).andCheckSchema().run();
+  }
+
+  @Test
+  @TestSecurity(user = OTHERUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_getForUser_failure_notFound() {
+    EndpointTestBuilder.from(GET_USER_NOT_FOUND).run();
+  }
+
+  @Test
+  @TestSecurity(user = OTHERUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_getForUser_failure_notFound_validateResponseFormat() {
+    EndpointTestBuilder.from(GET_USER_NOT_FOUND).andCheckFormat().run();
+  }
+
+  @Test
+  @TestSecurity(user = OTHERUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testGet_getForUser_failure_notFound_validateSchema() {
+    EndpointTestBuilder.from(GET_USER_NOT_FOUND).andCheckSchema().run();
+  }
+
+  @Test
+  void testGet_getForUser_failure_badCreds() {
+    EndpointTestBuilder.from(FOR_USER_BAD_CREDS).run();
+  }
+
+  @Test
+  @TestSecurity(user = OTHERUSER_PROFILE, roles = "profile")
+  void testGet_getForUser_failure_noValidRole() {
+    EndpointTestBuilder.from(FOR_USER_BAD_ROLE).run();
+  }
+
+  /*
+   * DELETE FOR USER
+   */
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testDelete_deleteForUser_success() {
+    EndpointTestBuilder.from(REVOKE_SUCCESS).doDelete(null).run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testDelete_deleteForUser_failure_invalidUser() {
+    EndpointTestBuilder.from(REVOKE_INVALID_USER).doDelete(null).run();
+  }
+
+  @Test
+  @TestSecurity(user = FAKEUSER_PROFILE, roles = "role")
+  void testDelete_deleteForUser_failure_invalidRole() {
+    EndpointTestBuilder.from(REVOKE_INVALID_ROLE).doDelete(null).run();
+  }
+
+  @Test
+  @TestSecurity(user = OTHERUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testDelete_deleteForUser_failure_noDoc() {
+    EndpointTestBuilder.from(REVOKE_NO_DOC).doDelete(null).run();
+  }
+
+  @Test
+  @TestSecurity(user = OTHERUSER_PROFILE, roles = OpenVSXParameters.DEFAULT_ACCESS_ROLE)
+  void testDelete_deleteForUser_failure_noDoc_validateSchema() {
+    EndpointTestBuilder.from(REVOKE_NO_DOC).doDelete(null).andCheckSchema().run();
+  }
+
+  @Test
+  void testDelete_deleteForUser_failure_badCreds() {
+    EndpointTestBuilder.from(FOR_USER_BAD_CREDS).run();
+  }
+
+  private String generateSigningSample(String ghHandle) {
+    try {
+      return mapper.writeValueAsString(new AgreementSigningRequest("1", ghHandle));
+    } catch (JsonProcessingException e) {
+      throw new RuntimeException(e);
     }
+  }
 }
-- 
GitLab