Commit fb078889 authored by Martin Lowe's avatar Martin Lowe

Merge branch 'malowe/master/1' into 'master'

Initial commit of commons code (WIP)

See merge request !1
parents f78513df 444d29ec
target/
.settings/
.project
.classpath
bin/
\ No newline at end of file
# Build JAVA applications using Apache Maven (http://maven.apache.org)
# For docker image tags see https://hub.docker.com/_/maven/
#
# For general lifecycle information see https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
# This template will build and test your projects
# * Caches downloaded dependencies and plugins between invocation.
# * Verify but don't deploy merge requests.
# * Deploy built artifacts from master branch only.
variables:
# This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Djava.awt.headless=true"
# As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
# when running from the command line.
# `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"
# This template uses jdk8 for verifying and deploying images
image: maven:3.3.9-jdk-8
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
paths:
- .m2/repository
# For merge requests do not `deploy` but only run `verify`.
# See https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
.verify: &verify
stage: test
script:
- 'mvn $MAVEN_CLI_OPTS verify -Dconfig.secret.path=config/sample.secret.properties'
except:
- master
# Verify merge requests using JDK8
verify:jdk8:
<<: *verify
# To deploy packages from CI, create a ci_settings.xml file
# For deploying packages to GitLab's Maven Repository: See https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for more details.
# Please note: The GitLab Maven Repository is currently only available in GitLab Premium / Ultimate.
# For `master` branch run `mvn deploy` automatically.
deploy:jdk8:
stage: deploy
script:
- if [ ! -f ci_settings.xml ];
then echo "CI settings missing\! If deploying to GitLab Maven Repository, please see https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for instructions.";
fi
- 'mvn $MAVEN_CLI_OPTS deploy -s ci_settings.xml'
only:
- master
quarkus.oauth2.client-id=sample
quarkus.oauth2.client-secret=sample
%test.sample.secret.property=secret-value
%test.eclipse.secret.token=example
\ No newline at end of file
quarkus.oauth2.client-id=1b8R2q6ZE0MA7lISWxXbOeqL9
quarkus.oauth2.client-secret=bNzI5juUFRXjeiFgsM2x05XUY
quarkus.datasource.password = my-secret-pw
%test.sample.secret.property=secret-value
%test.eclipse.secret.token=example
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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>
<artifactId>quarkus-core</artifactId>
<name>Core - Runtime</name>
<parent>
<groupId>org.eclipsefoundation</groupId>
<artifactId>quarkus-commons</artifactId>
<version>0.1-BETA</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
</properties>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<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.quarkus</groupId>
<artifactId>quarkus-undertow</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logmanager</groupId>
<artifactId>jboss-logmanager</artifactId>
<version>2.1.14.Final</version>
</dependency>
</dependencies>
</project>
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.config;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.config.PropertyNamingStrategy;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.eclipsefoundation.core.helper.DateTimeHelper;
/**
* Updates JSONB config to use a naming convention when interacting with objects
* that match the API best practices set by internal documentation.
*
* @author Martin Lowe
*/
@Provider
public class JsonBConfig implements ContextResolver<Jsonb> {
@Override
public Jsonb getContext(Class<?> type) {
JsonbConfig config = new JsonbConfig();
// following strategy is defined as default by internal API guidelines
config.withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES)
.withDateFormat(DateTimeHelper.RAW_RFC_3339_FORMAT, null);
return JsonbBuilder.create(config);
}
}
package org.eclipsefoundation.core.config;
import java.util.function.Supplier;
import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
/**
* Custom override for production that can be enabled to set user roles to
* include the role set in the property, defaulting to admin access.
*
* @author Martin Lowe
*/
@ApplicationScoped
public class RoleAugmentor implements SecurityIdentityAugmentor {
// properties that allow this functionality to be configured
@ConfigProperty(name = "eclipse.oauth.override", defaultValue = "false")
boolean overrideRole;
@ConfigProperty(name = "eclipse.oauth.override.role", defaultValue = "marketplace_admin_access")
String overrideRoleName;
@Override
public int priority() {
return 0;
}
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
return context.runBlocking(build(identity));
}
private Supplier<SecurityIdentity> build(SecurityIdentity identity) {
if (overrideRole) {
// create a new builder and copy principal, attributes, credentials and roles
// from the original
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder()
.setPrincipal(identity.getPrincipal()).addAttributes(identity.getAttributes())
.addCredentials(identity.getCredentials()).addRoles(identity.getRoles());
// add custom role source here
builder.addRole(overrideRoleName);
// put the updated role in the future
return builder::build;
} else {
// put the unmodified identity in the future
return () -> identity;
}
}
}
\ No newline at end of file
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.config;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Reads in a set of secret configuration values from the secret.properties file
* in the resources folder. These values are only secret in that they are set to
* be ignored by Git and should be set on a per-server basis.
*
* ConfigSource implementation was used to enable the usage of the
* {@link ServiceLoader} to load the configurations.
*
* @author Martin Lowe
*/
public class SecretConfigSource implements ConfigSource {
private static final Logger LOGGER = LoggerFactory.getLogger(SecretConfigSource.class);
public static final String PROPERTY_NAME = "config.secret.path";
public static final String ENV_NAME = "CONFIG_SECRET_PATH";
private Map<String, String> secrets;
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Map<String, String> getProperties() {
if (secrets == null) {
this.secrets = new HashMap<>();
String secretPath = System.getProperty(PROPERTY_NAME);
// Fallback to checking env if not set in JVM
if (secretPath == null || secretPath.isEmpty()) {
secretPath = System.getenv(ENV_NAME);
}
if (secretPath == null || secretPath.isEmpty()) {
LOGGER.warn("Configuration '{}' not set, cannot generate secret properties.", PROPERTY_NAME);
return this.secrets;
}
// load the secrets file in
File f = new File(secretPath);
if (!f.exists() || !f.canRead()) {
LOGGER.error("File at path {} either does not exist or cannot be read", secretPath);
return this.secrets;
}
// read each of the lines of secret config that should be added
try (BufferedReader br = new BufferedReader(new FileReader(f))) {
Properties p = new Properties();
p.load(br);
secrets.putAll((Map) p);
} catch (IOException e) {
LOGGER.error("Error while reading in secrets configuration file.", e);
}
LOGGER.debug("Found secret keys: {}", secrets.keySet());
// add priority ordinal to map if missing. 260 ordinal sets the priority between
// container and environment variable priority.
secrets.computeIfAbsent(ConfigSource.CONFIG_ORDINAL, key -> "260");
}
return secrets;
}
@Override
public String getValue(String propertyName) {
return getProperties().get(propertyName);
}
@Override
public String getName() {
return "secret";
}
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.exception;
/**
* @author martin
*
*/
public class MaintenanceException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.health;
import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;
import org.eclipse.microprofile.health.Readiness;
/**
* Basic health check to check the responsiveness of the API on a whole.
*
* @author Martin Lowe
*/
@Readiness
@Liveness
@ApplicationScoped
public class BaseApplicationHealthCheck implements HealthCheck {
@Override
public HealthCheckResponse call() {
return HealthCheckResponse.named("Basic health check").up().build();
}
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.helper;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Central implementation for handling date time conversion in the service.
* Class uses Java8 DateTime formatters, creating an internal format that
* represents RFC 3339
*
* @author Martin Lowe
*/
public class DateTimeHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(DateTimeHelper.class);
public static final String RAW_RFC_3339_FORMAT = "uuuu-MM-dd'T'HH:mm:ssXXX";
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(RAW_RFC_3339_FORMAT);
/**
* Converts RFC 3339 compliant date string to date object. If non compliant
* string is passed, issue is logged and null is returned. If negative UTC
* timezone (-00:00) is passed, UTC time zone is assumed.
*
* @param dateString an RFC 3339 date string.
* @return a date object representing time in date string, or null if not in RFC
* 3339 format.
*/
public static Date toRFC3339(String dateString) {
if (dateString.isBlank()) return null;
try {
return Date.from(ZonedDateTime.parse(dateString, formatter).toInstant());
} catch (DateTimeParseException e) {
LOGGER.warn("Could not parse date from string '{}'", dateString, e);
return null;
}
}
/**
* Converts passed date to RFC 3339 compliant date string. Time is adjusted to
* be in UTC time.
*
* @param date the date object to convert to RFC 3339 format.
* @return the RFC 3339 format date string.
*/
public static String toRFC3339(Date date) {
if (date == null) return null;
return formatter.format(date.toInstant().atZone(ZoneId.of("UTC")));
}
// hide constructor
private DateTimeHelper() {
}
}
package org.eclipsefoundation.core.helper;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.ws.rs.core.MultivaluedMap;
import org.eclipsefoundation.core.namespace.UrlParameterNamespace;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import io.quarkus.runtime.Startup;
/**
* Helper for parameter lookups and filtering.
*
* @author Martin Lowe
*
*/
@Startup
@ApplicationScoped
public class ParameterHelper {
@Inject
Instance<UrlParameterNamespace> urlParamImpls;
// maintain internal list of key names to reduce churn. These are only created
// at build time
private List<String> urlParamNames;
/**
* Create the list of parameter key names once upon creation of this class
*/
@PostConstruct
void initialize() {
// use array list for better random access + lookups
this.urlParamNames = new ArrayList<>();
// populate a list with each
urlParamImpls.forEach(
namespace -> namespace.getParameters().stream().forEach(param -> urlParamNames.add(param.getName())));
}
/**
* Filters out unknown parameters by name/key. Creates and returns a new map
* with the known/tracked parameters. Tracked parameters are added through
* implementations of the {@link UrlParameterNamespace} interface.
*
* @param src multivalued parameter map to filter
* @return a map containing the filtered parameter values
*/
public MultivaluedMap<String, String> filterUnknownParameters(MultivaluedMap<String, String> src) {
MultivaluedMap<String, String> out = new MultivaluedMapImpl<>();
Set<String> keys = src.keySet();
for (String key : keys) {
if (urlParamNames.contains(key)) {
out.addAll(key, src.get(key));
}
}
return out;
}
/**
* Returns a list of parameter names, as generated from reading in the
* parameters present in the {@link UrlParameterNamespace} implementations.
*
* @return list of tracked parameter names.
*/
public List<String> getValidParameters() {
return new ArrayList<>(urlParamNames);
}
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.helper;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.json.bind.Jsonb;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.xml.bind.DatatypeConverter;
import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.core.service.CachingService;
/**
* Helper class that transforms data into a response usable for the RESTeasy
* container. Uses injected JSON-B serializer and caching service to get current
* information on cache data.
*
* @author Martin Lowe
*
*/
@ApplicationScoped
public class ResponseHelper {
private static final MessageDigest DIGEST;
static {
try {
DIGEST = MessageDigest.getInstance("md5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Could not create an MD5 hash digest");
}