diff --git a/core/pom.xml b/core/pom.xml index 0b0009b465dd858bba7f36396d789ca427175061..b4fa21c47610907823a0aa21e1772055111bd8e8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -46,6 +46,17 @@ org.apache.commons commons-lang3 + + + commons-collections + commons-collections + 3.2.2 + + + + io.quarkus + quarkus-resteasy-qute + io.quarkus diff --git a/core/src/main/java/org/eclipsefoundation/core/service/StartupProxy.java b/core/src/main/java/org/eclipsefoundation/core/model/StartupProxy.java similarity index 95% rename from core/src/main/java/org/eclipsefoundation/core/service/StartupProxy.java rename to core/src/main/java/org/eclipsefoundation/core/model/StartupProxy.java index 2583a570afd42f7cc365216a7bcd5fc94083498f..ba72c0f7e865bd5c5c7b9eec4a9bec1843a46bae 100644 --- a/core/src/main/java/org/eclipsefoundation/core/service/StartupProxy.java +++ b/core/src/main/java/org/eclipsefoundation/core/model/StartupProxy.java @@ -9,7 +9,7 @@ * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -package org.eclipsefoundation.core.service; +package org.eclipsefoundation.core.model; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/org/eclipsefoundation/core/namespace/OptionalPath.java b/core/src/main/java/org/eclipsefoundation/core/namespace/OptionalPath.java new file mode 100644 index 0000000000000000000000000000000000000000..8ac81605df935fed3113af5cea2e0e735032d3e9 --- /dev/null +++ b/core/src/main/java/org/eclipsefoundation/core/namespace/OptionalPath.java @@ -0,0 +1,21 @@ +package org.eclipsefoundation.core.namespace; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(METHOD) +public @interface OptionalPath { + /** + * Name of the configuration value to check for whether the targeted resource path should be allowed to continue + * processing. + * + * @return the full path of the configuration value for enabling/disabling the given endpoint. + */ + String value(); + + boolean enabledByDefault() default false; +} diff --git a/core/src/main/java/org/eclipsefoundation/core/request/OptionalPathFilter.java b/core/src/main/java/org/eclipsefoundation/core/request/OptionalPathFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..e62e0019048cc510beadd2c01810a3807da87616 --- /dev/null +++ b/core/src/main/java/org/eclipsefoundation/core/request/OptionalPathFilter.java @@ -0,0 +1,62 @@ +package org.eclipsefoundation.core.request; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Optional; + +import javax.enterprise.inject.Instance; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipsefoundation.core.namespace.OptionalPath; +import org.jboss.resteasy.core.interception.jaxrs.PostMatchContainerRequestContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Does lookups on the current path to see if the resource is disabled. + * + * @author Martin Lowe + * + */ +@Provider +public class OptionalPathFilter implements ContainerRequestFilter { + public static final Logger LOGGER = LoggerFactory.getLogger(OptionalPathFilter.class); + + @ConfigProperty(name = "eclipse.optional-resources.enabled", defaultValue = "false") + Instance optionalResourcesEnabled; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + // check annotation on target endpoint to be sure that endpoint is enabled + Method m = ((PostMatchContainerRequestContext) requestContext).getResourceMethod().getMethod(); + OptionalPath opt = m.getAnnotation(OptionalPath.class); + if (opt != null) { + if (!optionalResourcesEnabled.get()) { + LOGGER.trace("Request to '{}' rejected as optional resources are not enabled", + requestContext.getUriInfo().getAbsolutePath()); + // abort with 404 as we should hide that this exists + requestContext.abortWith(Response.status(404).build()); + } + // get the specified config value fresh and check that it is enabled, or enabled by default if missing + Optional configValue = ConfigProvider.getConfig().getOptionalValue(opt.value(), Boolean.class); + if (configValue.isPresent() && configValue.get()) { + LOGGER.trace("Request to '{}' enabled by config, allowing call", + requestContext.getUriInfo().getAbsolutePath()); + } else if (configValue.isEmpty() && opt.enabledByDefault()) { + LOGGER.trace("Request to '{}' enabled by default, allowing call", + requestContext.getUriInfo().getAbsolutePath()); + } else { + LOGGER.trace("Request to '{}' rejected as endpoint is not enabled", + requestContext.getUriInfo().getAbsolutePath()); + // abort with 404 as we should hide that this exists + requestContext.abortWith(Response.status(404).build()); + } + } + + } +} diff --git a/core/src/main/java/org/eclipsefoundation/core/resource/CacheResource.java b/core/src/main/java/org/eclipsefoundation/core/resource/CacheResource.java new file mode 100644 index 0000000000000000000000000000000000000000..3bc7b4c3d9a1aac0a460ffd787a5075540c33d4c --- /dev/null +++ b/core/src/main/java/org/eclipsefoundation/core/resource/CacheResource.java @@ -0,0 +1,177 @@ +package org.eclipsefoundation.core.resource; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +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.MultivaluedMap; +import javax.ws.rs.core.Response; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.eclipsefoundation.core.namespace.MicroprofilePropertyNames; +import org.eclipsefoundation.core.namespace.OptionalPath; +import org.eclipsefoundation.core.service.CachingService; +import org.eclipsefoundation.core.service.CachingService.ParameterizedCacheKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +import io.quarkus.qute.Location; +import io.quarkus.qute.Template; +import io.quarkus.runtime.Startup; + +@Path("/caches") +@Produces(MediaType.APPLICATION_JSON) +public class CacheResource { + static final Logger LOGGER = LoggerFactory.getLogger(CacheResource.class); + + @Inject + InstanceCacheResourceKey key; + + // Qute templates, generates cache key interface + @Location("cache_keys") + Template cacheKeysView; + + @Inject + CachingService service; + + @GET + @Path("keys") + @Produces(MediaType.TEXT_HTML) + @OptionalPath(MicroprofilePropertyNames.CACHE_RESOURCE_ENABLED) + public Response getCaches(@QueryParam("key") String passedKey) { + if (shouldBlockCacheRequest(passedKey)) { + return Response.status(403).build(); + } + return Response + .ok() + .entity(cacheKeysView + .data("cacheKeys", service.getCacheKeys().stream().map(this::buildWrappedKeys).collect(Collectors.toList()), "key", + passedKey) + .render()) + .build(); + } + + @POST + @Path("{cacheKey}/clear") + @OptionalPath(MicroprofilePropertyNames.CACHE_RESOURCE_ENABLED) + public Response clearForKey(@PathParam("cacheKey") String cacheKey, @HeaderParam("x-cache-key") String passedKey, + Map> params) { + if (shouldBlockCacheRequest(passedKey)) { + return Response.status(403).build(); + } + // remove the given keys from the cache + List keys = service + .getCacheKeys() + .stream() + .filter(k -> k.getId().equals(cacheKey) && checkParams(params, k.getParams())) + .collect(Collectors.toList()); + keys.forEach(k -> service.remove(k)); + return Response.ok().entity(keys.size()).build(); + } + + /** + * Converts cache keys to wrapped cache keys with their expiration time. + * + * @param k the key to wrap + * @return the wrapped key, containing the passed key and its time to live. + */ + private WrappedCacheKey buildWrappedKeys(ParameterizedCacheKey k) { + Date d = null; + try { + d = new Date(service.getExpiration(k.getId(), k.getParams(), k.getClazz()).orElse(service.getMaxAge())); + } catch (RuntimeException e) { + // intentionally empty + } + return WrappedCacheKey.builder().setTtl(d).setKey(k).build(); + } + + /** + * Compare 2 parameter collections to check if they match. + * + * @param passedParams the passed params we need to match + * @param entryParams the current entry to checks params + * @return true if they match (insensitive to order), false otherwise + */ + private boolean checkParams(Map> passedParams, MultivaluedMap entryParams) { + if (passedParams.size() != entryParams.size()) { + return false; + } + return passedParams.entrySet().stream().allMatch(e -> CollectionUtils.isEqualCollection(e.getValue(), entryParams.get(e.getKey()))); + } + + /** + * Checks the access key to see if the current request should be blocked. + * + * @param passedKey the key passed by the requester + * @return true if request should be blocked, false otherwise. + */ + private boolean shouldBlockCacheRequest(String passedKey) { + return StringUtils.isBlank(passedKey) || !passedKey.equals(key.key); + } + + /** + * Cache key plus its expiration to be displayed on the view more page. + * + * @author Martin Lowe + * + */ + @AutoValue + @JsonDeserialize(builder = AutoValue_CacheResource_WrappedCacheKey.Builder.class) + public abstract static class WrappedCacheKey { + public abstract ParameterizedCacheKey getKey(); + + @Nullable + public abstract Date getTtl(); + + public static Builder builder() { + return new AutoValue_CacheResource_WrappedCacheKey.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setKey(ParameterizedCacheKey key); + + public abstract Builder setTtl(@Nullable Date ttl); + + public abstract WrappedCacheKey build(); + } + } + + /** + * Contains the random key generated on system start. Creates a random secure key that will be used to gate access to + * the cache resources endpoint. + * + * @author Martin Lowe + * + */ + @Startup + @Singleton + static class InstanceCacheResourceKey { + static final Logger LOGGER = LoggerFactory.getLogger(InstanceCacheResourceKey.class); + final String key; + + InstanceCacheResourceKey() { + this.key = UUID.randomUUID().toString(); + LOGGER.info("Generated cache resource key: {}", key); + } + } +} diff --git a/core/src/main/java/org/eclipsefoundation/core/response/PaginatedResultsFilter.java b/core/src/main/java/org/eclipsefoundation/core/response/PaginatedResultsFilter.java index 23fd873e9e4956b6e46bee3329b51992dd6a7063..94f43209481b845b98e4e057fc83e3036461c9c5 100644 --- a/core/src/main/java/org/eclipsefoundation/core/response/PaginatedResultsFilter.java +++ b/core/src/main/java/org/eclipsefoundation/core/response/PaginatedResultsFilter.java @@ -76,19 +76,14 @@ public class PaginatedResultsFilter implements ContainerResponseFilter { ResourceInfo resourceInfo; @Override - public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) - throws IOException { - - Method method = resourceInfo.getResourceMethod(); - Pagination annotation = method.getAnnotation(Pagination.class); + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { - if ((annotation == null || annotation.value()) && Boolean.TRUE.equals(enablePagination.get())) { + if (checkPaginationAnnotation() && Boolean.TRUE.equals(enablePagination.get())) { Object entity = responseContext.getEntity(); // only try and paginate if there are multiple entities if (entity instanceof Set) { - paginateResults(responseContext, - (new TreeSet<>((Set) entity)).stream().collect(Collectors.toList())); + paginateResults(responseContext, (new TreeSet<>((Set) entity)).stream().collect(Collectors.toList())); } else if (entity instanceof List) { paginateResults(responseContext, (List) entity); } @@ -110,13 +105,31 @@ public class PaginatedResultsFilter implements ContainerResponseFilter { if (listEntity.size() > pageSize) { // set the sliced array as the entity responseContext - .setEntity(listEntity.subList(getArrayLimitedNumber(listEntity, Math.max(0, page - 1) * pageSize), - getArrayLimitedNumber(listEntity, pageSize * page))); + .setEntity(listEntity + .subList(getArrayLimitedNumber(listEntity, Math.max(0, page - 1) * pageSize), + getArrayLimitedNumber(listEntity, pageSize * page))); } // set the link header to the response responseContext.getHeaders().add("Link", createLinkHeader(page, lastPage)); } + /** + * Checks the current method for pagination annotations and if present, checks to make sure pagination is enabled. + * Defaults to using pagination if the pagination annotation is missing or cannot be read. + * + * @return true if pagination should be enabled, false otherwise + */ + private boolean checkPaginationAnnotation() { + + // method can be null sometimes for quarkus native endpoints + Method method = resourceInfo.getResourceMethod(); + if (method != null) { + Pagination annotation = method.getAnnotation(Pagination.class); + return annotation == null || annotation.value(); + } + return true; + } + /** * Gets a header value if available, checking first the servlet response then the mutable response wrapper. * @@ -230,8 +243,8 @@ public class PaginatedResultsFilter implements ContainerResponseFilter { * * @param list the list to bind the number by * @param num the number to check for exceeding bounds. - * @return the passed number if its within the size of the given array, 0 if the number is negative, and the array - * size if greater than the maximum bounds. + * @return the passed number if its within the size of the given array, 0 if the number is negative, and the array size + * if greater than the maximum bounds. */ private int getArrayLimitedNumber(List list, int num) { return Math.min(list.size(), Math.max(0, num)); diff --git a/core/src/main/java/org/eclipsefoundation/core/service/StartupProxyService.java b/core/src/main/java/org/eclipsefoundation/core/service/StartupProxyService.java index acde361bfca8db82efe59593fe4d028754271e8a..e1cf9a836aed5a3ac0e2b385ee43d2cf71cb548b 100644 --- a/core/src/main/java/org/eclipsefoundation/core/service/StartupProxyService.java +++ b/core/src/main/java/org/eclipsefoundation/core/service/StartupProxyService.java @@ -1,41 +1,32 @@ -/********************************************************************* -* Copyright (c) 2022 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 -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ +/** + * Copyright (c) 2022 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: Martin Lowe + * + * SPDX-License-Identifier: EPL-2.0 + */ package org.eclipsefoundation.core.service; -import javax.annotation.PostConstruct; -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.Instance; -import javax.inject.Inject; +import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.quarkus.runtime.Startup; +import org.eclipsefoundation.core.model.StartupProxy; /** - * Finds services extending StartupProxy and ensures they are loaded at startup. - * This ensures that beans served using the provider pattern are found and - * default values are loaded. + * Interface for binding classes that can't have startup annotations to initialize at startup. + * + * @author Martin Lowe + * */ -@Startup -@ApplicationScoped -public class StartupProxyService { - static final Logger LOGGER = LoggerFactory.getLogger(StartupProxyService.class); - - @Inject - Instance services; +public interface StartupProxyService { - @PostConstruct - void init() { - services.forEach(StartupProxy::startupProxy); - } + /** + * Returns a list of proxied bean classes. + * + * @return list of beans detected as using the startup proxy. + */ + List> getProxiedBeanTypes(); } diff --git a/core/src/main/java/org/eclipsefoundation/core/service/impl/DefaultStartupProxyService.java b/core/src/main/java/org/eclipsefoundation/core/service/impl/DefaultStartupProxyService.java new file mode 100644 index 0000000000000000000000000000000000000000..ad72e5d90a8b2a281cab4958a2fb1435732c51ad --- /dev/null +++ b/core/src/main/java/org/eclipsefoundation/core/service/impl/DefaultStartupProxyService.java @@ -0,0 +1,55 @@ +/********************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +**********************************************************************/ +package org.eclipsefoundation.core.service.impl; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.eclipsefoundation.core.model.StartupProxy; +import org.eclipsefoundation.core.service.StartupProxyService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.quarkus.runtime.Startup; + +/** + * Finds services extending StartupProxy and ensures they are loaded at startup. This ensures that beans served using + * the provider pattern are found and default values are loaded. + * + * To properly bind in cases where the services are managed by a bean provider class, the interface of the service + * should be extended with the StartupProxy, rather than marking the actual implementation with an additional implements + * call. + */ +@Startup +@ApplicationScoped +public class DefaultStartupProxyService implements StartupProxyService { + static final Logger LOGGER = LoggerFactory.getLogger(DefaultStartupProxyService.class); + + @Inject + Instance services; + + @PostConstruct + void init() { + services.forEach(StartupProxy::startupProxy); + } + + @Override + public List> getProxiedBeanTypes() { + return services.stream().map(StartupProxy::getClass).collect(Collectors.toList()); + } + +} diff --git a/core/src/main/java/org/eclipsefoundation/core/service/impl/QuarkusCachingService.java b/core/src/main/java/org/eclipsefoundation/core/service/impl/QuarkusCachingService.java index dbc03aefecd905f7220e567a1dea01bded12f8e4..31d7fbb290c251b4cd75588d8c174ba2d21e0f4e 100644 --- a/core/src/main/java/org/eclipsefoundation/core/service/impl/QuarkusCachingService.java +++ b/core/src/main/java/org/eclipsefoundation/core/service/impl/QuarkusCachingService.java @@ -39,7 +39,8 @@ import io.quarkus.cache.CacheResult; import io.quarkus.cache.CaffeineCache; /** - * Utililzes Quarkus caching extensions in order to cache and retrieve data. + * Utililzes Quarkus caching extensions in order to cache and retrieve data. Reason we augment over the Quarkus core + * cache (caffeine) is so that we can record TTLs for cache objects which aren't exposed by base cache. * * @author Martin Lowe * @@ -58,12 +59,10 @@ public class QuarkusCachingService implements CachingService { Cache cache; @Override - public Optional get(String id, MultivaluedMap params, Class rawType, - Callable callable) { + public Optional get(String id, MultivaluedMap params, Class rawType, Callable callable) { Objects.requireNonNull(callable); - ParameterizedCacheKey cacheKey = new ParameterizedCacheKey(rawType, id, - params != null ? params : new MultivaluedMapImpl<>()); + ParameterizedCacheKey cacheKey = new ParameterizedCacheKey(rawType, id, params != null ? params : new MultivaluedMapImpl<>()); LOGGER.debug("Retrieving cache value for '{}'", cacheKey); return Optional.ofNullable(get(cacheKey, callable)); } @@ -101,8 +100,7 @@ public class QuarkusCachingService implements CachingService { @Override public void fuzzyRemove(String id, Class type) { - this.getCacheKeys().stream().filter(k -> k.getId().equals(id) && k.getClazz().equals(type)) - .forEach(this::remove); + this.getCacheKeys().stream().filter(k -> k.getId().equals(id) && k.getClazz().equals(type)).forEach(this::remove); } @Override @@ -136,21 +134,22 @@ public class QuarkusCachingService implements CachingService { @CacheResult(cacheName = "default") public T get(@CacheKey ParameterizedCacheKey cacheKey, Callable callable) { + // attempt to get the result for the cache entry T out = null; try { out = callable.call(); - // set internal expiration cache - getExpiration(false, cacheKey); } catch (Exception e) { LOGGER.error("Error while creating cache entry for key '{}': \n", cacheKey, e); } + // need to make sure that the expiration is always set for a cache value since we accept null + getExpiration(false, cacheKey); return out; } @CacheResult(cacheName = "ttl") - public long getExpiration(boolean throwIfMissing, @CacheKey ParameterizedCacheKey cacheKey) throws Exception { + public long getExpiration(boolean throwIfMissing, @CacheKey ParameterizedCacheKey cacheKey) { if (throwIfMissing) { - throw new Exception("No TTL present for cache key '{}', not generating"); + throw new RuntimeException("No TTL present for cache key '{}', not generating"); } LOGGER.debug("Timeout for {}: {}", cacheKey, System.currentTimeMillis() + getMaxAge()); return System.currentTimeMillis() + getMaxAge(); diff --git a/core/src/main/java/org/eclipsefoundation/core/template/MapToJSONExtension.java b/core/src/main/java/org/eclipsefoundation/core/template/MapToJSONExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..c45feca4c63165481638a5f81bd8c602f1fec51e --- /dev/null +++ b/core/src/main/java/org/eclipsefoundation/core/template/MapToJSONExtension.java @@ -0,0 +1,17 @@ +package org.eclipsefoundation.core.template; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.core.MultivaluedMap; + +import io.quarkus.qute.TemplateExtension; + +@TemplateExtension +public class MapToJSONExtension { + public static JsonObject toJson(MultivaluedMap params) { + JsonObjectBuilder out = Json.createObjectBuilder(); + params.forEach((k, v) -> out.add(k, Json.createArrayBuilder(v))); + return out.build(); + } +} diff --git a/core/src/main/resources/templates/cache_keys.html b/core/src/main/resources/templates/cache_keys.html new file mode 100644 index 0000000000000000000000000000000000000000..440156c684eb4ed240aad07740f3a4371f827b28 --- /dev/null +++ b/core/src/main/resources/templates/cache_keys.html @@ -0,0 +1,64 @@ +{#include eclipse_header /} +{||} + +
+

Cache keys

+ + + + + + + + + + {#for entry in cacheKeys.orEmpty} + + + + + + + + {/for} + +
IDParamsTypeTTLActions
{entry.key.id}{entry.key.params.toJson}{entry.key.clazz.getSimpleName}{entry.ttl or "Unknown"} + Clear +
+
+{||} +{#include eclipse_footer /} \ No newline at end of file diff --git a/core/src/main/resources/templates/eclipse_footer.html b/core/src/main/resources/templates/eclipse_footer.html new file mode 100644 index 0000000000000000000000000000000000000000..e9d8470450c2cc4a35b82afb044620b644168c14 --- /dev/null +++ b/core/src/main/resources/templates/eclipse_footer.html @@ -0,0 +1,62 @@ + +

+ Back to the top +

+
+ + + + + diff --git a/core/src/main/resources/templates/eclipse_header.html b/core/src/main/resources/templates/eclipse_header.html new file mode 100644 index 0000000000000000000000000000000000000000..d01c0463c82789859c2966911460fbc22ae32349 --- /dev/null +++ b/core/src/main/resources/templates/eclipse_header.html @@ -0,0 +1,75 @@ + + + + + + + +{||} + + + + HTML Template | The Eclipse Foundation + + + + + + + + + + + + + + + + + + {||} + + Skip to main content +
+ +
\ No newline at end of file diff --git a/core/src/test/java/org/eclipsefoundation/core/resource/CacheResourceDisabledTest.java b/core/src/test/java/org/eclipsefoundation/core/resource/CacheResourceDisabledTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2093aa7865e3a0fc7bfe4130ef809e5f3a5fb7b3 --- /dev/null +++ b/core/src/test/java/org/eclipsefoundation/core/resource/CacheResourceDisabledTest.java @@ -0,0 +1,30 @@ +package org.eclipsefoundation.core.resource; + +import static io.restassured.RestAssured.given; + +import java.util.UUID; + +import javax.inject.Inject; + +import org.eclipsefoundation.core.resource.CacheResource.InstanceCacheResourceKey; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class CacheResourceDisabledTest { + + @Inject + InstanceCacheResourceKey key; + + @Test + void cacheUI_protected() { + given().when().get("/caches/keys").then().statusCode(404); + } + + @Test + void cacheUI_returnsMissingWithKeyValues() { + given().when().get("/caches/keys?key={key}", UUID.randomUUID().toString()).then().statusCode(404); + given().when().get("/caches/keys?key={key}", key.key).then().statusCode(404); + } +} diff --git a/core/src/test/java/org/eclipsefoundation/core/resource/CacheResourceTest.java b/core/src/test/java/org/eclipsefoundation/core/resource/CacheResourceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0270103cf93e8d73aafcc17c3bf6553441bdcb99 --- /dev/null +++ b/core/src/test/java/org/eclipsefoundation/core/resource/CacheResourceTest.java @@ -0,0 +1,54 @@ +package org.eclipsefoundation.core.resource; + +import static io.restassured.RestAssured.given; + +import java.util.UUID; + +import javax.inject.Inject; + +import org.eclipsefoundation.core.resource.CacheResource.InstanceCacheResourceKey; +import org.eclipsefoundation.core.test.OptionalResourceEnabledTestProfile; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.arc.Arc; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; + +@QuarkusTest +@TestProfile(OptionalResourceEnabledTestProfile.class) +public class CacheResourceTest { + + @Inject + InstanceCacheResourceKey key; + + @Test + void resourceSecuringKey_changesEachCreation() { + InstanceCacheResourceKey k = new InstanceCacheResourceKey(); + InstanceCacheResourceKey k2 = new InstanceCacheResourceKey(); + Assertions.assertNotEquals(k.key, k2.key); + } + + @Test + void resourceSecuringKey_availableThroughCDI() { + Assertions.assertNotNull(key.key); + } + + @Test + void resourceSecuringKey_sharedLifecycle() { + // check that key doesn't change across calls + InstanceCacheResourceKey keyCdiRef = Arc.container().instance(InstanceCacheResourceKey.class).get(); + Assertions.assertEquals(keyCdiRef.key, key.key); + } + + @Test + void cacheUI_protected() { + given().when().get("/caches/keys").then().statusCode(403); + } + + @Test + void cacheUI_usesInstanceKey() { + given().when().get("/caches/keys?key={key}", UUID.randomUUID().toString()).then().statusCode(403); + given().when().get("/caches/keys?key={key}", key.key).then().statusCode(200); + } +} diff --git a/core/src/test/java/org/eclipsefoundation/core/test/OptionalResourceEnabledTestProfile.java b/core/src/test/java/org/eclipsefoundation/core/test/OptionalResourceEnabledTestProfile.java new file mode 100644 index 0000000000000000000000000000000000000000..ca6ed3bebe10f0f0ebda986c990a3ff3ce256b45 --- /dev/null +++ b/core/src/test/java/org/eclipsefoundation/core/test/OptionalResourceEnabledTestProfile.java @@ -0,0 +1,30 @@ +package org.eclipsefoundation.core.test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.eclipsefoundation.core.namespace.MicroprofilePropertyNames; + +import io.quarkus.test.junit.QuarkusTestProfile; + +/** + * + * @author Martin Lowe + */ +public class OptionalResourceEnabledTestProfile implements QuarkusTestProfile { + + // private immutable copy of the configs for auth state + private static final Map CONFIG_OVERRIDES; + static { + Map tmp = new HashMap<>(); + tmp.put(MicroprofilePropertyNames.OPTIONAL_RESOURCES_ENABLED, "true"); + tmp.put(MicroprofilePropertyNames.CACHE_RESOURCE_ENABLED, "true"); + CONFIG_OVERRIDES = Collections.unmodifiableMap(tmp); + } + + @Override + public Map getConfigOverrides() { + return CONFIG_OVERRIDES; + } +} diff --git a/core/src/test/java/org/eclipsefoundation/core/test/services/FooBarService.java b/core/src/test/java/org/eclipsefoundation/core/test/services/FooBarService.java index 08e82c9d626401c7c7b3ea08cf3d24689cab7976..66056412d598414788d54bc463d235f77a98a9c9 100644 --- a/core/src/test/java/org/eclipsefoundation/core/test/services/FooBarService.java +++ b/core/src/test/java/org/eclipsefoundation/core/test/services/FooBarService.java @@ -11,7 +11,7 @@ **********************************************************************/ package org.eclipsefoundation.core.test.services; -import org.eclipsefoundation.core.service.StartupProxy; +import org.eclipsefoundation.core.model.StartupProxy; public interface FooBarService extends StartupProxy { String getName(); diff --git a/persistence/deployment/pom.xml b/persistence/deployment/pom.xml index 6151f597089af72417e2db36f9a63e122e650e69..1b45f1122b5368a2a2ed249a77ec96d662b290bf 100644 --- a/persistence/deployment/pom.xml +++ b/persistence/deployment/pom.xml @@ -54,6 +54,10 @@ io.quarkus quarkus-jdbc-mariadb-deployment
+ + io.quarkus + quarkus-resteasy-qute-deployment +