diff --git a/.gitignore b/.gitignore index 5b19cb510be3d966e9ba7ccf260b70a6fc08f226..89c5f5e5d92547f081c1c3ed4fe0f855f48bba21 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ nb-configuration.xml # Local environment /volumes .env +config/mariadb/main/init.d/eclipsefoundation.sql /config/**/*secret.properties /node_modules diff --git a/Makefile b/Makefile index e259db79cd2880f3c3bf91742eef45c9db0dc3a7..67f9e3478097f0b8d59da3967a331b39b05c0019 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SHELL = /bin/bash pre-setup:; @echo "Creating environment file from template" @rm -f .env && envsubst < config/.env.sample > .env - [ -f ./config/mariadb/init.d/eclipsefoundation.sql ] && echo "EF DB dump already exists, skipping fetch" || scp api-vm1:~/webdev/sql/eclipsefoundation.sql ./config/mariadb/init.d/ + [ -f ./config/mariadb/main/init.d/eclipsefoundation.sql ] && echo "EF DB dump already exists, skipping fetch" || (scp api-vm1:~/webdev/sql/eclipsefoundation.sql.gz ./config/mariadb/main/init.d/ && gzip -d ./config/mariadb/main/init.d/eclipsefoundation.sql.gz) setup:; @echo "Generating secret files from templates using environment file + variables" diff --git a/config/mariadb/init.d/init.sql b/config/mariadb/main/init.d/init.sql similarity index 100% rename from config/mariadb/init.d/init.sql rename to config/mariadb/main/init.d/init.sql diff --git a/docker-compose.yaml b/docker-compose.yaml index 88e830e13eb3385266e0b561c4a936d5204ca3be..7915d878460522c1e03a5f22a089988c46924217 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -50,6 +50,6 @@ services: MYSQL_ROOT_PASSWORD: ${MARIADB_PASSWORD} MYSQL_DATABASE: ${KEYCLOAK_DB} volumes: - - ./config/mariadb/init.d:/docker-entrypoint-initdb.d + - ./config/mariadb/main/init.d:/docker-entrypoint-initdb.d - ./volumes/mariadb:/var/lib/mysql - ./config/mariadb/conf:/etc/mysql/conf.d diff --git a/pom.xml b/pom.xml index 5024ebb7007b583d489d7abc7d6e7083f740498f..e4dc83483eb94f1f82294fa1da0e103ec8a88f6c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,7 @@ <?xml version="1.0"?> -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<project + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>org.eclipsefoundation</groupId> <artifactId>eclipse-openvsx-api</artifactId> @@ -10,14 +12,13 @@ <maven.compiler.release>11</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> - <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> + <quarkus.platform.artifact-id> quarkus-bom</quarkus.platform.artifact-id> <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id> - <quarkus.platform.version>2.11.2.Final</quarkus.platform.version> + <quarkus.platform.version> 2.14.2.Final</quarkus.platform.version> <surefire-plugin.version>3.0.0-M5</surefire-plugin.version> <auto-value.version>1.8.2</auto-value.version> - <hibernate.version>5.5.6.Final</hibernate.version> - <eclipse-api-version>0.7.0-SNAPSHOT</eclipse-api-version> - <fdndb-api-version>1.0-SNAPSHOT</fdndb-api-version> + <eclipse-api-version> 0.7.1</eclipse-api-version> + <fdndb-api-version>1.0.3</fdndb-api-version> </properties> <repositories> <repository> @@ -62,111 +63,31 @@ <artifactId>foundationdb-api-client</artifactId> <version>${fdndb-api-version}</version> </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-resteasy</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-resteasy-jackson</artifactId> - </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-oidc-client</artifactId> </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-oidc-client-filter</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-rest-client</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-cache</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-arc</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-hibernate-validator</artifactId> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-text</artifactId> - <version>1.9</version> - </dependency> <!-- Annotation preprocessors - reduce all of the boiler plate --> - <dependency> - <groupId>com.google.auto.value</groupId> - <artifactId>auto-value</artifactId> - <version>${auto-value.version}</version> - <scope>provided</scope> - </dependency> <dependency> <groupId>com.google.auto.value</groupId> <artifactId>auto-value-annotations</artifactId> <version>${auto-value.version}</version> </dependency> - <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>jsr305</artifactId> - <version>3.0.0</version> - </dependency> <!-- Testing dependencies only --> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-junit5</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>io.rest-assured</groupId> - <artifactId>rest-assured</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>io.rest-assured</groupId> - <artifactId>json-schema-validator</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-junit5-mockito</artifactId> - <scope>test</scope> - </dependency> <dependency> <groupId>org.eclipsefoundation</groupId> <artifactId>quarkus-test-common</artifactId> <version>${eclipse-api-version}</version> - </dependency> - - <!-- Following H2/devservices deps are made to circumvent need for docker --> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-devservices-h2</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-jdbc-h2</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>com.h2database</groupId> - <artifactId>h2</artifactId> - <scope>test</scope> - </dependency> - <!-- Flyway specific dependencies, used to setup tables in test --> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-flyway</artifactId> + <artifactId>quarkus-junit5</artifactId> <scope>test</scope> </dependency> + </dependencies> <build> <plugins> @@ -235,10 +156,8 @@ </goals> <configuration> <systemPropertyVariables> - <native.image.path> - ${project.build.directory}/${project.build.finalName}-runner</native.image.path> - <java.util.logging.manager> - org.jboss.logmanager.LogManager</java.util.logging.manager> + <native.image.path> ${project.build.directory}/${project.build.finalName}-runner</native.image.path> + <java.util.logging.manager> org.jboss.logmanager.LogManager</java.util.logging.manager> <maven.home>${maven.home}</maven.home> </systemPropertyVariables> </configuration> @@ -248,8 +167,7 @@ </plugins> </build> <properties> - <quarkus.package.type> - native</quarkus.package.type> + <quarkus.package.type> native</quarkus.package.type> </properties> </profile> </profiles> diff --git a/spec/openapi.yaml b/spec/openapi.yaml index 1f22b2cc85a2666f83b6d8933cdda231d35dc91f..955e3303c2c16f9b65020eddb8a041ceca0e9415 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -29,6 +29,10 @@ paths: $ref: "#/components/schemas/PublisherAgreement" 404: description: Agreement not found. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" 500: description: Error while retrieving data post: @@ -158,3 +162,18 @@ components: github_handle: type: string description: The GitHub username of the user. This must match what the Eclipse Foundation has on file for the user to successfully sign the puglisher agreement. + + Error: + type: object + properties: + status_code: + type: integer + description: HTTP response code + message: + type: string + description: Message containing error information + url: + oneOf: + - type: string + - type: "null" + description: The URL diff --git a/src/main/java/org/eclipsefoundation/openvsx/api/DrupalOAuthAPI.java b/src/main/java/org/eclipsefoundation/openvsx/api/DrupalOAuthAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..c68ed5f8c7d83b21f7def793ad5e045af2845864 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/api/DrupalOAuthAPI.java @@ -0,0 +1,36 @@ +/********************************************************************* +* 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.api; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipsefoundation.openvsx.api.models.DrupalOAuthData; + +/** + * Drupal OAuth2 token validation API binding + */ +@Path("oauth2/tokens") +@Produces(MediaType.APPLICATION_JSON) +@ApplicationScoped +@RegisterRestClient(configKey = "accounts-api") +public interface DrupalOAuthAPI { + + @GET + @Path("{token}") + DrupalOAuthData getTokenInfo(@PathParam("token") String token); +} \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/openvsx/api/EclipseAPI.java b/src/main/java/org/eclipsefoundation/openvsx/api/EclipseAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..39659d78e03b3196c9e679dbb066f4005818db2c --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/api/EclipseAPI.java @@ -0,0 +1,37 @@ +/********************************************************************* +* 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.api; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipsefoundation.openvsx.api.models.EfUser; + +@ApplicationScoped +@Produces("application/json") +@RegisterRestClient(configKey = "eclipse-api") +public interface EclipseAPI { + + /** + * Retrieves user objects that matches the given Github username. + * + * @param username GH handle to match against EF user + * @return the matching Eclipse account or null + */ + @GET + @Path("/github/profile/{username}") + EfUser getUserByGithubName(@PathParam("username") String username); +} diff --git a/src/main/java/org/eclipsefoundation/openvsx/api/PeopleAPI.java b/src/main/java/org/eclipsefoundation/openvsx/api/PeopleAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..6e6377b2b277b9e63c9a0042bd2ce75c85b6883b --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/api/PeopleAPI.java @@ -0,0 +1,56 @@ +/********************************************************************* +* 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.api; + +import java.util.List; + +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipsefoundation.foundationdb.client.model.PeopleDocumentData; + +import io.quarkus.oidc.client.filter.OidcClientFilter; + +@Path("people") +@OidcClientFilter +@Produces(MediaType.APPLICATION_JSON) +@RegisterRestClient(configKey = "fdndb-api") +@ApplicationScoped +public interface PeopleAPI { + + @GET + @Path("{personId}/documents") + @RolesAllowed("fdb_read_people_documents") + List<PeopleDocumentData> getPeopleDocuments(@PathParam("personId") String personId, + @QueryParam("documentID") String documentId); + + @PUT + @Path("{personId}/documents") + @RolesAllowed("fdb_write_people_documents") + List<PeopleDocumentData> createPeopleDocument(@PathParam("personId") String personId, PeopleDocumentData src); + + @DELETE + @Path("{personId}/documents/{documentId}/{effectiveDate}") + @RolesAllowed("fdb_write_people_documents") + Response deletePeopleDocument(@PathParam("personId") String personId, @PathParam("documentId") String documentId, + @PathParam("effectiveDate") String effectiveDate); +} diff --git a/src/main/java/org/eclipsefoundation/openvsx/api/models/AgreementSigningRequest.java b/src/main/java/org/eclipsefoundation/openvsx/api/models/AgreementSigningRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..1b6a9bd15667f7455959f49b61e401d8418690a7 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/api/models/AgreementSigningRequest.java @@ -0,0 +1,40 @@ +/********************************************************************* +* 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.api.models; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +@AutoValue +@JsonDeserialize(builder = AutoValue_AgreementSigningRequest.Builder.class) +public abstract class AgreementSigningRequest { + + public abstract String getVersion(); + + public abstract String getGithubHandle(); + + public static Builder builder() { + return new AutoValue_AgreementSigningRequest.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + + public abstract Builder setVersion(String version); + + public abstract Builder setGithubHandle(String handle); + + public abstract AgreementSigningRequest build(); + } +} diff --git a/src/main/java/org/eclipsefoundation/openvsx/api/models/DrupalOAuthData.java b/src/main/java/org/eclipsefoundation/openvsx/api/models/DrupalOAuthData.java new file mode 100644 index 0000000000000000000000000000000000000000..917479b96318572043ca7104c516a476d7031c2d --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/api/models/DrupalOAuthData.java @@ -0,0 +1,55 @@ +/********************************************************************* +* 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.api.models; + +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +@AutoValue +@JsonDeserialize(builder = AutoValue_DrupalOAuthData.Builder.class) +public abstract class DrupalOAuthData { + + public abstract String getClientId(); + + @Nullable + public abstract String getUserId(); + + public abstract String getAccessToken(); + + public abstract long getExpires(); + + public abstract String getScope(); + + public static Builder builder() { + return new AutoValue_DrupalOAuthData.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + + public abstract Builder setClientId(String id); + + public abstract Builder setUserId(@Nullable String id); + + public abstract Builder setAccessToken(String token); + + public abstract Builder setExpires(long expires); + + public abstract Builder setScope(String scope); + + public abstract DrupalOAuthData build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/openvsx/api/models/EfUser.java b/src/main/java/org/eclipsefoundation/openvsx/api/models/EfUser.java new file mode 100644 index 0000000000000000000000000000000000000000..6f3dd60d2db8b55642fabe66313dc83eaf8f56fd --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/api/models/EfUser.java @@ -0,0 +1,40 @@ +/********************************************************************* +* 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.api.models; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +@AutoValue +@JsonDeserialize(builder = AutoValue_EfUser.Builder.class) +public abstract class EfUser { + + public abstract String getName(); + + public abstract String getGithubHandle(); + + public static Builder builder() { + return new AutoValue_EfUser.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + + public abstract Builder setName(String name); + + public abstract Builder setGithubHandle(String handle); + + public abstract EfUser build(); + } +} diff --git a/src/main/java/org/eclipsefoundation/openvsx/helpers/DrupalAuthHelper.java b/src/main/java/org/eclipsefoundation/openvsx/helpers/DrupalAuthHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..57d7e1e43c4bd865cfddd1f2aff62eba8ba4a203 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/helpers/DrupalAuthHelper.java @@ -0,0 +1,65 @@ +/********************************************************************* +* 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.helpers; + +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Defines helper methods for Drupal OAuth2 token validation. + */ +public class DrupalAuthHelper { + + /** + * Validates whether a given expiry date has passed. + * + * @param expiryDate The token expiry date in seconds since epoch. + * @return True if expiry date is older than current time. False if not. + */ + public static boolean isExpired(long expiryDate) { + return expiryDate <= Instant.now().getEpochSecond(); + } + + /** + * Validates whether the token scopes and valid scopes are the same. + * + * @param tokenScope The space-separated token scopes. + * @param validScopes The list of valid scopes. + * @return Returns false if any token scopes are not in the list of valid + * scopes. True if all match. + */ + public static boolean hasScopes(String tokenScope, List<String> validScopes) { + + List<String> tokenScopes = Arrays.asList(tokenScope.split(" ")); + + Collections.sort(tokenScopes); + Collections.sort(validScopes); + + return tokenScopes.containsAll(validScopes); + } + + /** + * Validates whether the the token's client id matches a desired client id. + * + * @param clientId The client id associated with the token + * @param validClientIds The valid client ids allowed in the service. + * @return True if ids match. False if not. + */ + public static boolean hasValidclientId(String clientId, List<String> validClientIds) { + return validClientIds.stream().anyMatch(id -> id.equalsIgnoreCase(clientId)); + } + + private DrupalAuthHelper() { + } +} \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/openvsx/request/OAuthFilter.java b/src/main/java/org/eclipsefoundation/openvsx/request/OAuthFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..b9f05e61d9c6a05b2f8f455398b9b76459d1d651 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/request/OAuthFilter.java @@ -0,0 +1,83 @@ +/********************************************************************* +* 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.request; + +import java.io.IOException; +import java.util.List; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipsefoundation.core.exception.FinalForbiddenException; +import org.eclipsefoundation.openvsx.api.models.DrupalOAuthData; +import org.eclipsefoundation.openvsx.services.DrupalOAuthService; +import org.jboss.resteasy.util.HttpHeaderNames; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Provider +public class OAuthFilter implements ContainerRequestFilter { + private static final Logger LOGGER = LoggerFactory.getLogger(OAuthFilter.class); + + public static final String TOKEN_STATUS = "tokenStatus"; + + @ConfigProperty(name = "eclipse.openvsx.scopes", defaultValue = "openvsx_publisher_agreement") + Instance<List<String>> validScopes; + + @ConfigProperty(name = "eclipse.openvsx.clients") + Instance<List<String>> validClientIds; + + @ConfigProperty(name = "eclipse.openvsx.oauth-filter.enabled", defaultValue = "false") + Instance<Boolean> isEnabled; + + @Inject + DrupalOAuthService oauthService; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + if (Boolean.TRUE.equals(isEnabled.get())) { + String token = stripBearerToken(requestContext.getHeaderString(HttpHeaderNames.AUTHORIZATION)); + DrupalOAuthData tokenStatus = oauthService.validateTokenStatus(token, validScopes.get(), + validClientIds.get()); + if (tokenStatus != null && tokenStatus.getUserId() != null) { + requestContext.setProperty(TOKEN_STATUS, tokenStatus); + } + } + } + + /** + * Strips the bearer token down to just the token. Throws a + * FinalForbiddenException if the token is in an invalid format. + * + * @param header the bearer token suthorization header + * @return The raw token string. + */ + private String stripBearerToken(String header) { + if (header == null || header.equals("")) { + throw new FinalForbiddenException("Invalid token"); + } + + String[] splits = header.split(" "); + + // Invalid token if no space or more than one space + if (splits.length != 2 || !splits[0].equals("Bearer")) { + LOGGER.error("Invalid auth header: {}", header); + throw new FinalForbiddenException("Invalid token"); + } + + return splits[1]; + } +} diff --git a/src/main/java/org/eclipsefoundation/openvsx/resources/GreetingResource.java b/src/main/java/org/eclipsefoundation/openvsx/resources/GreetingResource.java deleted file mode 100644 index f85612ff7962ec49652ba3e668f47ee9193fb3a5..0000000000000000000000000000000000000000 --- a/src/main/java/org/eclipsefoundation/openvsx/resources/GreetingResource.java +++ /dev/null @@ -1,24 +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.resources; - -import javax.ws.rs.GET; -import javax.ws.rs.Path; - -@Path("/hello") -public class GreetingResource { - - @GET - public String hello() { - return "Hello RESTEasy"; - } -} \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java b/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java new file mode 100644 index 0000000000000000000000000000000000000000..95f8555a191b24936e0a0fd99cf0311b555ec6db --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResource.java @@ -0,0 +1,95 @@ +/********************************************************************* +* 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.resources; + +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; + +import org.eclipsefoundation.core.exception.FinalForbiddenException; +import org.eclipsefoundation.foundationdb.client.model.PeopleDocumentData; +import org.eclipsefoundation.openvsx.api.models.AgreementSigningRequest; +import org.eclipsefoundation.openvsx.api.models.DrupalOAuthData; +import org.eclipsefoundation.openvsx.request.OAuthFilter; +import org.eclipsefoundation.openvsx.services.PublisherAgreementService; + +@Path("publisher_agreement") +public class PublisherAgreementResource { + + @Context + HttpServletRequest request; + + @Inject + PublisherAgreementService agreementService; + + @GET + public Response getAgreement() { + // Endpoint uses currently logged in user. Token must have user_id + return getAgreementForUser(extractUserIdFromToken()); + } + + @POST + public Response createAgreement(AgreementSigningRequest body) { + + // Endpoint uses currently logged in user. Token must have user_id + PeopleDocumentData result = agreementService.createPublisherAgreement(extractUserIdFromToken(), body); + if (result == null) { + throw new BadRequestException("Unable to create Publisher Agreement with current request parameters"); + } + + return Response.ok(result).build(); + } + + @GET + @Path("{efUsername}") + public Response getAgreementForUser(@PathParam("efUsername") String username) { + PeopleDocumentData result = agreementService.getPublisherAgreement(username); + if (result == null) { + throw new NotFoundException(String.format("Unable to find agreement for user: %s", username)); + } + + return Response.ok(result).build(); + } + + @DELETE + @Path("{efUsername}") + public Response deleteAgreementForUser(@PathParam("efUsername") String username) { + if (!agreementService.deletePublisherAgreement(username)) { + throw new BadRequestException(String.format("Unable to delete agreement for user: %s", username)); + } + + return Response.ok().build(); + } + + /** + * Extracts the user_id from the token data in order to determine a user is + * signed in. Throws a FinalForbiddenException if there is no user associated + * with this token. + * + * @return The token user id. + */ + private String extractUserIdFromToken() { + DrupalOAuthData tokenStatus = (DrupalOAuthData) request.getAttribute(OAuthFilter.TOKEN_STATUS); + if (tokenStatus == null || tokenStatus.getUserId() == null) { + throw new FinalForbiddenException("No user associated with this token"); + } + return tokenStatus.getUserId(); + } +} \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/openvsx/services/DrupalOAuthService.java b/src/main/java/org/eclipsefoundation/openvsx/services/DrupalOAuthService.java new file mode 100644 index 0000000000000000000000000000000000000000..d544c79ccf838e62cbf4f7f8993804d603d4103f --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/services/DrupalOAuthService.java @@ -0,0 +1,34 @@ +/********************************************************************* +* 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.services; + +import java.util.List; + +import org.eclipsefoundation.openvsx.api.models.DrupalOAuthData; + +/** + * Defines a Drupal Oauth validation service. + */ +public interface DrupalOAuthService { + + /** + * Validates an OAuth2 token using a list of valid scopes. Also validates token + * expiry and client id. Returns an OAuthTokenStatus entity containing token + * validity information as well as the basis for denial if token deemed invalid. + * + * @param token The token string. + * @param validScopes A list of valid scopes for this token. + * @param validClientId A valid client_id for this token. + * @return A OAuthTokenStatus entity containing status info. + */ + DrupalOAuthData validateTokenStatus(String token, List<String> validScopes, List<String> validClientIds); +} \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/openvsx/services/PublisherAgreementService.java b/src/main/java/org/eclipsefoundation/openvsx/services/PublisherAgreementService.java new file mode 100644 index 0000000000000000000000000000000000000000..77531710bfd8df50e3604308decce8269cb273a3 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/services/PublisherAgreementService.java @@ -0,0 +1,54 @@ +/********************************************************************* +* 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.services; + +import org.eclipsefoundation.foundationdb.client.model.PeopleDocumentData; +import org.eclipsefoundation.openvsx.api.models.AgreementSigningRequest; + +/** + * Defines the service for fetching, creating and deleting openvsx publisher + * agreements. + */ +public interface PublisherAgreementService { + + /** + * Retrieves the most recent openvsx publisher agreement for the given user. + * + * @param username The desired user's EF username. + * @return A PeopleDocumentData entity or null. + */ + public PeopleDocumentData getPublisherAgreement(String username); + + /** + * Attempts to create a publisher agreement for the given user using the request + * body contining the user GH handle and document version. Validates the GH + * handle exists and is tied to the desired username. Returns null if the GH + * handle is incorrect, if it can't create the signed document, or if it + * encounters an error. + * + * @param username The desired user's EF username. + * @param request The rrequest body containing the GH handle and coument + * version. + * @return A PeopleDocumentData entity or null. + */ + public PeopleDocumentData createPublisherAgreement(String username, AgreementSigningRequest request); + + /** + * Attempts to delete the most recent publisher agreement for the given user and + * removes the entry from the cache. Returns false if document can not be found. + * Returns true if deletion was successful. + * + * @param username the desired user's EF username. + * @return True if success, false if not. + */ + public boolean deletePublisherAgreement(String username); +} diff --git a/src/main/java/org/eclipsefoundation/openvsx/services/impl/DefaultDrupalOAuthService.java b/src/main/java/org/eclipsefoundation/openvsx/services/impl/DefaultDrupalOAuthService.java new file mode 100644 index 0000000000000000000000000000000000000000..191f845e526d6ba0ef7f09d6d8365a68ede0d68a --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/services/impl/DefaultDrupalOAuthService.java @@ -0,0 +1,59 @@ +/********************************************************************* +* 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.services.impl; + +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.core.exception.FinalForbiddenException; +import org.eclipsefoundation.openvsx.api.DrupalOAuthAPI; +import org.eclipsefoundation.openvsx.api.models.DrupalOAuthData; +import org.eclipsefoundation.openvsx.helpers.DrupalAuthHelper; +import org.eclipsefoundation.openvsx.services.DrupalOAuthService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +public class DefaultDrupalOAuthService implements DrupalOAuthService { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDrupalOAuthService.class); + + @Inject + @RestClient + DrupalOAuthAPI oauthAPI; + + @Override + public DrupalOAuthData validateTokenStatus(String token, List<String> validScopes, List<String> validClientIds) { + try { + LOGGER.debug("Validating token: {}", token); + + DrupalOAuthData tokenData = oauthAPI.getTokenInfo(token); + + if (DrupalAuthHelper.isExpired(tokenData.getExpires())) { + throw new FinalForbiddenException("The Authorization token is expired"); + } + if (!DrupalAuthHelper.hasScopes(tokenData.getScope(), validScopes)) { + throw new FinalForbiddenException("The Authorization token does not have the required permissions/scopes"); + } + if (!DrupalAuthHelper.hasValidclientId(tokenData.getClientId(), validClientIds)) { + throw new FinalForbiddenException("The Authorization token has an Invalid client_id"); + } + + return tokenData; + } catch (Exception e) { + LOGGER.error("Error validating token", e); + throw new FinalForbiddenException("Error validating token"); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/openvsx/services/impl/DefaultPublisherAgreementService.java b/src/main/java/org/eclipsefoundation/openvsx/services/impl/DefaultPublisherAgreementService.java new file mode 100644 index 0000000000000000000000000000000000000000..c2f8b61d28e3ce17eb1ede007a63f0405b52f8ed --- /dev/null +++ b/src/main/java/org/eclipsefoundation/openvsx/services/impl/DefaultPublisherAgreementService.java @@ -0,0 +1,129 @@ +/********************************************************************* +* 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.services.impl; + +import java.util.Date; +import java.util.List; +import java.util.Optional; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.core.helper.DateTimeHelper; +import org.eclipsefoundation.core.service.CachingService; +import org.eclipsefoundation.core.service.CachingService.ParameterizedCacheKey; +import org.eclipsefoundation.foundationdb.client.model.PeopleDocumentData; +import org.eclipsefoundation.openvsx.api.EclipseAPI; +import org.eclipsefoundation.openvsx.api.PeopleAPI; +import org.eclipsefoundation.openvsx.api.models.AgreementSigningRequest; +import org.eclipsefoundation.openvsx.api.models.EfUser; +import org.eclipsefoundation.openvsx.services.PublisherAgreementService; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +public class DefaultPublisherAgreementService implements PublisherAgreementService { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPublisherAgreementService.class); + + @ConfigProperty(name = "eclipse.openvsx.doc-id") + String openvsxDocId; + + @Inject + @RestClient + PeopleAPI peopleAPI; + + @Inject + @RestClient + EclipseAPI eclipseAPI; + + @Inject + CachingService cache; + + @Override + public PeopleDocumentData getPublisherAgreement(String username) { + try { + Optional<List<PeopleDocumentData>> results = cache.get(username, new MultivaluedMapImpl<>(), + PeopleDocumentData.class, () -> peopleAPI.getPeopleDocuments(username, openvsxDocId)); + + if (results.isEmpty() || results.get().isEmpty()) { + LOGGER.error("Unable to find agreements for user with name: {}", username); + return null; + } + + // Sort by date. Returning most recent + return results.get().stream() + .sorted((pDoc1, pDoc2) -> pDoc2.getEffectiveDate().compareTo(pDoc1.getEffectiveDate())) + .findFirst().orElse(null); + } catch (Exception e) { + LOGGER.error("Error while fetching publisher agreements for user: {}", username, e); + return null; + } + } + + @Override + public PeopleDocumentData createPublisherAgreement(String username, AgreementSigningRequest body) { + try { + Optional<EfUser> ghResult = cache.get("gh: " + body.getGithubHandle(), new MultivaluedMapImpl<>(), + Response.class, () -> eclipseAPI.getUserByGithubName(body.getGithubHandle())); + + if (ghResult.isEmpty() || ghResult.get() == null) { + LOGGER.error("Unable to find account for GH user: {}", body.getGithubHandle()); + return null; + } + + // Ensure username in URL matches GH fetch username + if (!ghResult.get().getName().equalsIgnoreCase(username)) { + LOGGER.error("Invalid GH handle for use: {}", username); + return null; + } + + PeopleDocumentData fdnDbRequest = PeopleDocumentData.builder() + .setPersonID(username) + .setDocumentID(openvsxDocId) + .setVersion(Double.parseDouble(body.getVersion())) + .setEffectiveDate(new Date()) + .setReceivedDate(new Date()) + .build(); + + // Submit new document to fdnDb for creation, can fail if invalid version is set + List<PeopleDocumentData> results = peopleAPI.createPeopleDocument(username, fdnDbRequest); + if (results == null || results.isEmpty()) { + LOGGER.error("Unable to create agreement for user with name: {}", username); + return null; + } + + return results.get(0); + + } catch (Exception e) { + LOGGER.error("Error while creating publisher agreement", e); + return null; + } + } + + @Override + public boolean deletePublisherAgreement(String username) { + // Get most recent document if it exists + PeopleDocumentData mostRecent = getPublisherAgreement(username); + if (mostRecent == null) { + return false; + } + + // Delete and remove from cache entry + peopleAPI.deletePeopleDocument(username, openvsxDocId, DateTimeHelper.toRFC3339(mostRecent.getEffectiveDate())); + cache.remove(new ParameterizedCacheKey(PeopleDocumentData.class, username, new MultivaluedMapImpl<>())); + return true; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 841a00313cfd4a91cc1a48cafd8ca307ffbe276f..0847c4171abc63f0c6fbe02702b6eaf248c22d79 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,8 +1,16 @@ quarkus.http.root-path=/openvsx -fdndb-api/mp-rest/url=http://localhost:8095 -fdndb-api/mp-rest/scope=javax.inject.Singleton +fdndb-api/mp-rest/url=http://foundationdb:8095 +%dev.fdndb-api/mp-rest/url=http://localhost:10112 + +accounts-api/mp-rest/url=https://accounts.eclipse.org +%dev.accounts-api/mp-rest/url=https://accounts-staging.eclipse.org + +eclipse-api/mp-rest/url=https://api.eclipse.org quarkus.oidc.enabled=false +eclipse.openvsx.oauth-filter.enabled=true +%dev.eclipse.openvsx.oauth-filter.enabled=true + quarkus.log.level=INFO \ No newline at end of file diff --git a/src/test/java/org/eclipsefoundation/openvsx/helpers/DrupalAuthHelperTest.java b/src/test/java/org/eclipsefoundation/openvsx/helpers/DrupalAuthHelperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..097774335b9a807edb9c1123c68c097c55e1e4f8 --- /dev/null +++ b/src/test/java/org/eclipsefoundation/openvsx/helpers/DrupalAuthHelperTest.java @@ -0,0 +1,117 @@ +/********************************************************************* +* 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.helpers; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class DrupalAuthHelperTest { + + @Test + void testExpiry_notExpired() { + Assertions.assertFalse(DrupalAuthHelper.isExpired(Instant.now().getEpochSecond() + 20000)); + Assertions.assertFalse(DrupalAuthHelper.isExpired(Instant.now().getEpochSecond() + 3000)); + Assertions.assertFalse(DrupalAuthHelper.isExpired(Instant.now().getEpochSecond() + 100)); + } + + @Test + void testExpiry_expired() { + Assertions.assertTrue(DrupalAuthHelper.isExpired(Instant.now().getEpochSecond() - 20000)); + Assertions.assertTrue(DrupalAuthHelper.isExpired(Instant.now().getEpochSecond() - 3000)); + Assertions.assertTrue(DrupalAuthHelper.isExpired(Instant.now().getEpochSecond() - 100)); + } + + @Test + void testScope_success() { + List<String> validScopes = new ArrayList<>(); + validScopes.add("read"); + validScopes.add("write"); + validScopes.add("admin"); + + Assertions.assertTrue(DrupalAuthHelper.hasScopes("read write admin", validScopes)); + + validScopes = new ArrayList<>(); + validScopes.add("read"); + validScopes.add("write"); + + Assertions.assertTrue(DrupalAuthHelper.hasScopes("read write", validScopes)); + + validScopes = new ArrayList<>(); + validScopes.add("read"); + validScopes.add("admin"); + + Assertions.assertTrue(DrupalAuthHelper.hasScopes("read admin", validScopes)); + + validScopes = new ArrayList<>(); + validScopes.add("read"); + + Assertions.assertTrue(DrupalAuthHelper.hasScopes("read", validScopes)); + } + + @Test + void testScope_failure() { + List<String> validScopes = new ArrayList<>(); + validScopes.add("read"); + validScopes.add("write"); + validScopes.add("admin"); + + Assertions.assertFalse(DrupalAuthHelper.hasScopes("read write guy", validScopes)); + + validScopes = new ArrayList<>(); + validScopes.add("read"); + validScopes.add("write"); + + Assertions.assertFalse(DrupalAuthHelper.hasScopes("read", validScopes)); + + validScopes = new ArrayList<>(); + validScopes.add("read"); + validScopes.add("admin"); + + Assertions.assertFalse(DrupalAuthHelper.hasScopes("admin,read", validScopes)); + + validScopes = new ArrayList<>(); + validScopes.add("read"); + + Assertions.assertFalse(DrupalAuthHelper.hasScopes("admin", validScopes)); + } + + @Test + void testClientId_success() { + List<String> validIds = new ArrayList<>(); + validIds.add("client-id"); + validIds.add("test-id"); + validIds.add("valid-id"); + + Assertions.assertTrue(DrupalAuthHelper.hasValidclientId("client-id", validIds)); + Assertions.assertTrue(DrupalAuthHelper.hasValidclientId("test-id", validIds)); + Assertions.assertTrue(DrupalAuthHelper.hasValidclientId("valid-id", validIds)); + } + + @Test + void testClientId_failure() { + List<String> validIds = new ArrayList<>(); + validIds.add("client-id"); + validIds.add("test-id"); + validIds.add("valid-id"); + + Assertions.assertFalse(DrupalAuthHelper.hasValidclientId("invalid-id", validIds)); + Assertions.assertFalse(DrupalAuthHelper.hasValidclientId("not-id", validIds)); + Assertions.assertFalse(DrupalAuthHelper.hasValidclientId("no-valid", validIds)); + } +} \ No newline at end of file diff --git a/src/test/java/org/eclipsefoundation/openvsx/resources/GreetingResourceTest.java b/src/test/java/org/eclipsefoundation/openvsx/resources/GreetingResourceTest.java deleted file mode 100644 index 317b00f858d93688eadd1b2197d7390336589e61..0000000000000000000000000000000000000000 --- a/src/test/java/org/eclipsefoundation/openvsx/resources/GreetingResourceTest.java +++ /dev/null @@ -1,33 +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.resources; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; - -import org.junit.jupiter.api.Test; - -import io.quarkus.test.junit.QuarkusTest; - -@QuarkusTest -class GreetingResourceTest { - - @Test - void testHelloEndpoint() { - given() - .when().get("/hello") - .then() - .statusCode(200) - .body(is("Hello RESTEasy")); - } - -} \ No newline at end of file diff --git a/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java b/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c1ceb4c419af7c38f67a84acc8442c169fca2424 --- /dev/null +++ b/src/test/java/org/eclipsefoundation/openvsx/resources/PublisherAgreementResourceTest.java @@ -0,0 +1,237 @@ +/********************************************************************* +* 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.resources; + +import java.util.Map; +import java.util.Optional; + +import javax.inject.Inject; + +import org.eclipsefoundation.openvsx.api.models.AgreementSigningRequest; +import org.eclipsefoundation.openvsx.test.helpers.SchemaNamespaceHelper; +import org.eclipsefoundation.testing.helpers.TestCaseHelper; +import org.eclipsefoundation.testing.templates.RestAssuredTemplates; +import org.eclipsefoundation.testing.templates.RestAssuredTemplates.EndpointTestCase; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; + +@QuarkusTest +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 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) + .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) + .setHeaderParams(invalidCreds).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(userNoDocCreds).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).setHeaderParams(invalidCreds).build(); + + public static final EndpointTestCase DELETE_FOR_USER_INVALID_USER = TestCaseHelper + .prepareTestCase(USER_URL, new String[] { "other" }, SchemaNamespaceHelper.ERROR_SCHEMA_PATH) + .setStatusCode(400).setHeaderParams(userCreds).build(); + + @Inject + ObjectMapper mapper; + + /* + * GET CURRENT USER + */ + @Test + void testGet_currentUser_success() { + RestAssuredTemplates.testGet(GET_CURRENT_SUCCESS); + } + + @Test + void testGet_currentUser_success_validateResponseFormat() { + RestAssuredTemplates.testGet_validateResponseFormat(GET_CURRENT_SUCCESS); + } + + @Test + void testGet_currentUser_success_validateSchema() { + RestAssuredTemplates.testGet_validateResponseFormat(GET_CURRENT_SUCCESS); + } + + @Test + void testGet_currentUser_failure_notFound() { + RestAssuredTemplates.testGet(GET_CURRENT_NOT_FOUND); + } + + @Test + void testGet_currentUser_failure_notFound_validateResponseFormat() { + RestAssuredTemplates.testGet_validateResponseFormat(GET_CURRENT_NOT_FOUND); + } + + @Test + void testGet_currentUser_failure_notFound_validateSchema() { + RestAssuredTemplates.testGet_validateResponseFormat(GET_CURRENT_NOT_FOUND); + } + + @Test + void testGet_currentUser_failure_badCreds() { + RestAssuredTemplates.testGet(BAD_CREDS); + } + + /* + * POST CURRENT USER + */ + @Test + void testPost_currentUser_success() { + RestAssuredTemplates.testPost(GET_CURRENT_SUCCESS, generateSigningSample("fakeuser")); + } + + @Test + void testPost_currentUser_success_validateResponseFormat() { + RestAssuredTemplates.testPost_validateResponseFormat(GET_CURRENT_SUCCESS, generateSigningSample("fakeuser")); + } + + @Test + void testPost_currentUser_success_validateSchema() { + RestAssuredTemplates.testPost_validateResponseFormat(GET_CURRENT_SUCCESS, generateSigningSample("fakeuser")); + } + + @Test + void testPost_currentUser_failure_invalidRequestFormat() { + RestAssuredTemplates.testPost( + TestCaseHelper.prepareTestCase(BASE_URL, new String[] {}, null).setHeaderParams(userCreds) + .setRequestContentType(ContentType.TEXT).setStatusCode(500).build(), + generateSigningSample("fakeuser")); + } + + @Test + void testPost_currentUser_failure_invalidHandle() { + RestAssuredTemplates.testPost(POST_CURRENT_INVALID_HANDLE, generateSigningSample("otheruser")); + } + + @Test + void testPost_currentUser_failure_invalidHandle_validateFormat() { + RestAssuredTemplates.testPost_validateResponseFormat(POST_CURRENT_INVALID_HANDLE, + generateSigningSample("otheruser")); + } + + @Test + void testPost_currentUser_failure_badCreds() { + RestAssuredTemplates.testPost(BAD_CREDS, generateSigningSample("fakeuser")); + } + + /* + * GET FOR USER + */ + @Test + void testGet_getForUser_success() { + RestAssuredTemplates.testGet(GET_USER_SUCCESS); + } + + @Test + void testGet_geFortUser_success_validateResponseFormat() { + RestAssuredTemplates.testGet_validateResponseFormat(GET_USER_SUCCESS); + } + + @Test + void testGet_getForUser_success_validateSchema() { + RestAssuredTemplates.testGet_validateResponseFormat(GET_USER_SUCCESS); + } + + @Test + void testGet_getForUser_failure_notFound() { + RestAssuredTemplates.testGet(GET_USER_NOT_FOUND); + } + + @Test + void testGet_getForUser_failure_notFound_validateResponseFormat() { + RestAssuredTemplates.testGet_validateResponseFormat(GET_USER_NOT_FOUND); + } + + @Test + void testGet_getForUser_failure_notFound_validateSchema() { + RestAssuredTemplates.testGet_validateResponseFormat(GET_USER_NOT_FOUND); + } + + @Test + void testGet_getForUser_failure_badCreds() { + RestAssuredTemplates.testGet(FOR_USER_BAD_CREDS); + } + + /* + * DELETE FOR USER + */ + @Test + void testDelete_deleteForUser_success() { + RestAssuredTemplates.testDelete(GET_USER_SUCCESS, null); + } + + @Test + void testDelete_deleteForUser_failure_invalidUser() { + RestAssuredTemplates.testDelete(DELETE_FOR_USER_INVALID_USER, null); + } + + @Test + void testDelete_deleteForUser_failure_invalidUser_validateResponseFormat() { + RestAssuredTemplates.testDelete_validateResponseFormat(DELETE_FOR_USER_INVALID_USER, null); + } + + @Test + void testDelete_deleteForUser_failure_invalidUser_validateSchema() { + RestAssuredTemplates.testDelete_validateSchema(DELETE_FOR_USER_INVALID_USER, null); + } + + @Test + void testDelete_deleteForUser_failure_badCreds() { + RestAssuredTemplates.testGet(FOR_USER_BAD_CREDS); + } + + private String generateSigningSample(String ghHandle) { + try { + return mapper.writeValueAsString( + AgreementSigningRequest.builder().setVersion("1").setGithubHandle(ghHandle).build()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/eclipsefoundation/openvsx/services/DrupalOAuthServiceTest.java b/src/test/java/org/eclipsefoundation/openvsx/services/DrupalOAuthServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b5fd0ab457dd8fb452953e76f697f97a9b53ed60 --- /dev/null +++ b/src/test/java/org/eclipsefoundation/openvsx/services/DrupalOAuthServiceTest.java @@ -0,0 +1,112 @@ +/********************************************************************* +* 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.services; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import org.eclipsefoundation.core.exception.FinalForbiddenException; +import org.eclipsefoundation.openvsx.api.models.DrupalOAuthData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class DrupalOAuthServiceTest { + + @Inject + DrupalOAuthService oauthService; + + @Test + void testToken_success() { + List<String> validScopes = new ArrayList<>(); + validScopes.add("read"); + validScopes.add("write"); + validScopes.add("admin"); + + List<String> validIds = new ArrayList<>(); + validIds.add("client-id"); + validIds.add("test-id"); + validIds.add("valid-id"); + + DrupalOAuthData tokenData = oauthService.validateTokenStatus("token2", validScopes, validIds); + Assertions.assertEquals("token2", tokenData.getAccessToken()); + Assertions.assertEquals("test-id", tokenData.getClientId()); + + validScopes = new ArrayList<>(); + validScopes.add("read"); + validScopes.add("write"); + + tokenData = oauthService.validateTokenStatus("token4", validScopes, validIds); + Assertions.assertEquals("token4", tokenData.getAccessToken()); + Assertions.assertEquals("client-id", tokenData.getClientId()); + } + + @Test + void testToken_failure_expired() { + List<String> validScopes = new ArrayList<>(); + validScopes.add("read"); + validScopes.add("write"); + + List<String> validIds = new ArrayList<>(); + validIds.add("client-id"); + validIds.add("test-id"); + validIds.add("valid-id"); + + Assertions.assertThrows(FinalForbiddenException.class, + () -> oauthService.validateTokenStatus("token3", validScopes, validIds)); + + Assertions.assertThrows(FinalForbiddenException.class, + () -> oauthService.validateTokenStatus("token1", validScopes, validIds)); + } + + @Test + void testToken_failure_invalidScope() { + List<String> validScopes = new ArrayList<>(); + validScopes.add("admin"); + + List<String> validIds = new ArrayList<>(); + validIds.add("client-id"); + validIds.add("test-id"); + validIds.add("valid-id"); + + Assertions.assertThrows(FinalForbiddenException.class, + () -> oauthService.validateTokenStatus("token1", validScopes, validIds)); + + Assertions.assertThrows(FinalForbiddenException.class, + () -> oauthService.validateTokenStatus("token4", validScopes, validIds)); + } + + @Test + void testToken_failure_invalidClientId() { + List<String> scopes1 = new ArrayList<>(); + scopes1.add("read"); + scopes1.add("write"); + scopes1.add("admin"); + + List<String> validIds = new ArrayList<>(); + validIds.add("valid-id"); + + Assertions.assertThrows(FinalForbiddenException.class, + () -> oauthService.validateTokenStatus("token2", scopes1, validIds)); + + List<String> scopes2 = new ArrayList<>(); + scopes2.add("read"); + scopes2.add("write"); + + Assertions.assertThrows(FinalForbiddenException.class, + () -> oauthService.validateTokenStatus("token4", scopes2, validIds)); + } +} \ No newline at end of file diff --git a/src/test/java/org/eclipsefoundation/openvsx/test/api/MockDrupalOAuthAPI.java b/src/test/java/org/eclipsefoundation/openvsx/test/api/MockDrupalOAuthAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..deb49a3994ea012506327aa944f5269d174f9bfa --- /dev/null +++ b/src/test/java/org/eclipsefoundation/openvsx/test/api/MockDrupalOAuthAPI.java @@ -0,0 +1,75 @@ +/********************************************************************* +* 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 javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.openvsx.api.DrupalOAuthAPI; +import org.eclipsefoundation.openvsx.api.models.DrupalOAuthData; + +import io.quarkus.test.Mock; + +@Mock +@RestClient +@ApplicationScoped +public class MockDrupalOAuthAPI implements DrupalOAuthAPI { + + List<DrupalOAuthData> internal; + + public MockDrupalOAuthAPI() { + internal = new ArrayList<>(); + internal.addAll(Arrays.asList( + DrupalOAuthData.builder() + .setAccessToken("token1") + .setClientId("client-id") + .setExpires(1674111182) + .setScope("read write") + .build(), + DrupalOAuthData.builder() + .setAccessToken("token2") + .setClientId("test-id") + .setUserId("fakeuser") + .setExpires(Instant.now().getEpochSecond() + 20000) + .setScope("read write admin") + .build(), + DrupalOAuthData.builder() + .setAccessToken("token3") + .setClientId("test-id") + .setExpires(1234567890) + .setScope("read admin") + .build(), + DrupalOAuthData.builder() + .setAccessToken("token4") + .setClientId("client-id") + .setExpires(Instant.now().getEpochSecond() + 20000) + .setScope("read write") + .build(), + DrupalOAuthData.builder() + .setAccessToken("token5") + .setClientId("test-id") + .setUserId("name") + .setExpires(Instant.now().getEpochSecond() + 20000) + .setScope("read write admin") + .build())); + } + + @Override + public DrupalOAuthData getTokenInfo(String token) { + return internal.stream().filter(t -> t.getAccessToken().equalsIgnoreCase(token)).findFirst().orElse(null); + } +} \ No newline at end of file diff --git a/src/test/java/org/eclipsefoundation/openvsx/test/api/MockEclipseAPI.java b/src/test/java/org/eclipsefoundation/openvsx/test/api/MockEclipseAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..9ae448399e542ba61e115fdadabe3cab35e7827f --- /dev/null +++ b/src/test/java/org/eclipsefoundation/openvsx/test/api/MockEclipseAPI.java @@ -0,0 +1,58 @@ +/********************************************************************* +* 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.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.openvsx.api.EclipseAPI; +import org.eclipsefoundation.openvsx.api.models.EfUser; + +import io.quarkus.test.Mock; + +@Mock +@RestClient +@ApplicationScoped +public class MockEclipseAPI implements EclipseAPI { + + private List<EfUser> users; + + public MockEclipseAPI() { + this.users = new ArrayList<>(); + this.users.addAll(Arrays.asList( + EfUser.builder() + .setName("firstlast") + .setGithubHandle("handle") + .build(), + EfUser.builder() + .setName("fakeuser") + .setGithubHandle("fakeuser") + .build(), + EfUser.builder() + .setName("name") + .setGithubHandle("name") + .build(), + EfUser.builder() + .setName("testtesterson") + .setGithubHandle("mctesty") + .build())); + } + + @Override + public EfUser getUserByGithubName(String username) { + return users.stream().filter(u -> u.getGithubHandle().equalsIgnoreCase(username)).findFirst().orElse(null); + } +} diff --git a/src/test/java/org/eclipsefoundation/openvsx/test/api/MockPeopleAPI.java b/src/test/java/org/eclipsefoundation/openvsx/test/api/MockPeopleAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..a0e24fb445c67f6e4673244c68af3a5f62880f75 --- /dev/null +++ b/src/test/java/org/eclipsefoundation/openvsx/test/api/MockPeopleAPI.java @@ -0,0 +1,94 @@ +/********************************************************************* +* 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.Date; +import java.util.List; +import java.util.stream.Collectors; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.foundationdb.client.model.PeopleDocumentData; +import org.eclipsefoundation.openvsx.api.PeopleAPI; + +import io.quarkus.test.Mock; + +@Mock +@RestClient +@ApplicationScoped +public class MockPeopleAPI implements PeopleAPI { + + private List<PeopleDocumentData> peopleDocs; + + public MockPeopleAPI() { + this.peopleDocs = new ArrayList<>(); + this.peopleDocs.addAll(Arrays.asList( + PeopleDocumentData.builder() + .setPersonID("fakeuser") + .setDocumentID("sampleId") + .setVersion(1) + .setEffectiveDate(new Date()) + .build(), + PeopleDocumentData.builder() + .setPersonID("firstlast") + .setDocumentID("sampleId") + .setVersion(1) + .setEffectiveDate(new Date()) + .build(), + PeopleDocumentData.builder() + .setPersonID("name") + .setDocumentID("otherId") + .setVersion(1) + .setEffectiveDate(new Date()) + .build(), + PeopleDocumentData.builder() + .setPersonID("username") + .setDocumentID("sampleId") + .setVersion(1) + .setEffectiveDate(new Date()) + .build())); + } + + @Override + public List<PeopleDocumentData> getPeopleDocuments(String personId, String documentId) { + return peopleDocs.stream().filter( + d -> d.getPersonID().equalsIgnoreCase(personId) && d.getDocumentID().equalsIgnoreCase(documentId)) + .collect(Collectors.toList()); + } + + @Override + public List<PeopleDocumentData> createPeopleDocument(String personId, PeopleDocumentData src) { + peopleDocs.add(src); + return Arrays.asList(src); + } + + @Override + public Response deletePeopleDocument(String personId, String documentId, String effectiveDate) { + PeopleDocumentData result = peopleDocs.stream() + .filter(d -> d.getDocumentID().equalsIgnoreCase(documentId) + && d.getPersonID().equalsIgnoreCase(personId) + && d.getEffectiveDate() == Date.from(Instant.parse(effectiveDate))) + .findFirst().orElse(null); + + if (result == null) { + return Response.status(404).build(); + } + + peopleDocs.remove(result); + return Response.status(204).build(); + } +} diff --git a/src/test/java/org/eclipsefoundation/openvsx/test/helpers/SchemaNamespaceHelper.java b/src/test/java/org/eclipsefoundation/openvsx/test/helpers/SchemaNamespaceHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..53e6d9b44c33631bfcf8dab86a55a9b7ccc4dc5d --- /dev/null +++ b/src/test/java/org/eclipsefoundation/openvsx/test/helpers/SchemaNamespaceHelper.java @@ -0,0 +1,23 @@ +/********************************************************************* +* 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.helpers; + +public class SchemaNamespaceHelper { + public static final String BASE_SCHEMA_PATH = "schemas/"; + public static final String BASE_SCHEMA_PATH_SUFFIX = "-schema.json"; + + public static final String SIGNING_REQUEST_SCHEMA_PATH = BASE_SCHEMA_PATH + "agreement-signing-request" + + BASE_SCHEMA_PATH_SUFFIX; + public static final String PUBLISHER_AGREEMENT_SCHEMA_PATH = BASE_SCHEMA_PATH + "publisher-agreement" + + BASE_SCHEMA_PATH_SUFFIX; + public static final String ERROR_SCHEMA_PATH = BASE_SCHEMA_PATH + "error" + BASE_SCHEMA_PATH_SUFFIX; +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 8ca4d923a957ca9a5536a671cca483f16fcc2e63..f2c254ba6d8531653609487f74aba13069c390f0 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -2,4 +2,10 @@ quarkus.oauth2.enabled=false quarkus.oidc.enabled=false quarkus.keycloak.devservices.enabled=false -quarkus.oidc-client.enabled=false \ No newline at end of file +quarkus.oidc-client.enabled=false + +eclipse.openvsx.oauth-filter.enabled=true + +eclipse.openvsx.clients=test-id +eclipse.openvsx.scopes=read,write,admin +eclipse.openvsx.doc-id=sampleId \ No newline at end of file