commit bd2623d9131d9521a2b7c924b647a9c1c8916732 Author: Edward M. Kagan Date: Thu Apr 9 12:15:21 2020 +0300 Intial diff --git a/cayenne/deployment/pom.xml b/cayenne/deployment/pom.xml new file mode 100644 index 0000000..bce3719 --- /dev/null +++ b/cayenne/deployment/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + + org.pagan.quarkus + cayenne-parent + 1.0-SNAPSHOT + + + cayenne-deployment + ${project.artifactId} + + + + io.quarkus + quarkus-core-deployment + ${quarkus.platform.version} + + + io.quarkus + quarkus-agroal-deployment + ${quarkus.platform.version} + + + org.pagan.quarkus + cayenne + 1.0-SNAPSHOT + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + + + \ No newline at end of file diff --git a/cayenne/deployment/src/main/java/org/pagan/quarkus/cayenne/CayenneProcessor.java b/cayenne/deployment/src/main/java/org/pagan/quarkus/cayenne/CayenneProcessor.java new file mode 100644 index 0000000..c8b3004 --- /dev/null +++ b/cayenne/deployment/src/main/java/org/pagan/quarkus/cayenne/CayenneProcessor.java @@ -0,0 +1,36 @@ +package org.pagan.quarkus.cayenne; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.ServiceStartBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; + +public class CayenneProcessor { + + private CayenneConfig config; + + @BuildStep + FeatureBuildItem feature() { + System.out.println("CayenneProcessor - feature"); + return new FeatureBuildItem("cayenne"); + } + + @BuildStep + AdditionalBeanBuildItem beans() { + System.out.println("CayenneProcessor - beans"); + return AdditionalBeanBuildItem.unremovableOf(CayenneSupport.class); + } + + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep + void build(CayenneRecorder recorder, BuildProducer serviceStart, BeanContainerBuildItem beanContainer, ShutdownContextBuildItem shutdownContext) { + System.out.println("CayenneProcessor - build"); + recorder.initialize(config, beanContainer.getValue(), shutdownContext); + serviceStart.produce(new ServiceStartBuildItem("cayenne")); + } +} diff --git a/cayenne/pom.xml b/cayenne/pom.xml new file mode 100644 index 0000000..094decd --- /dev/null +++ b/cayenne/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + org.pagan.quarkus + extensions + 1.0-SNAPSHOT + + + cayenne-parent + ${project.artifactId} + pom + + + deployment + runtime + + + diff --git a/cayenne/runtime/pom.xml b/cayenne/runtime/pom.xml new file mode 100644 index 0000000..3db21cb --- /dev/null +++ b/cayenne/runtime/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + + org.pagan.quarkus + cayenne-parent + 1.0-SNAPSHOT + + + cayenne + ${project.artifactId} + + + + io.quarkus + quarkus-core + ${quarkus.platform.version} + + + io.quarkus + quarkus-agroal + ${quarkus.platform.version} + + + org.apache.cayenne + cayenne-server + ${cayenne.version} + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.platform.version} + + + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + + + \ No newline at end of file diff --git a/cayenne/runtime/src/main/java/org/pagan/quarkus/cayenne/CayenneConfig.java b/cayenne/runtime/src/main/java/org/pagan/quarkus/cayenne/CayenneConfig.java new file mode 100644 index 0000000..47a1d92 --- /dev/null +++ b/cayenne/runtime/src/main/java/org/pagan/quarkus/cayenne/CayenneConfig.java @@ -0,0 +1,26 @@ +package org.pagan.quarkus.cayenne; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Quarkus Apache Cayenne configuration holder + * @author Edward M. Kagan + */ +@ConfigRoot(name = "cayenne", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public final class CayenneConfig { + + /** + * Cayenne configuration file location + */ + @ConfigItem + public String config; + + /** + * Will Cayenne log SQL requests or not + */ + @ConfigItem(defaultValue = "false") + public boolean log; + +} \ No newline at end of file diff --git a/cayenne/runtime/src/main/java/org/pagan/quarkus/cayenne/CayenneRecorder.java b/cayenne/runtime/src/main/java/org/pagan/quarkus/cayenne/CayenneRecorder.java new file mode 100644 index 0000000..f5f911c --- /dev/null +++ b/cayenne/runtime/src/main/java/org/pagan/quarkus/cayenne/CayenneRecorder.java @@ -0,0 +1,16 @@ +package org.pagan.quarkus.cayenne; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class CayenneRecorder { + + public void initialize(CayenneConfig config, BeanContainer container, ShutdownContext shutdownContext) { + CayenneSupport support = container.instance(CayenneSupport.class); + support.initialize(config); + shutdownContext.addShutdownTask(() -> support.shutdown()); + } + +} diff --git a/cayenne/runtime/src/main/java/org/pagan/quarkus/cayenne/CayenneSupport.java b/cayenne/runtime/src/main/java/org/pagan/quarkus/cayenne/CayenneSupport.java new file mode 100644 index 0000000..493f2da --- /dev/null +++ b/cayenne/runtime/src/main/java/org/pagan/quarkus/cayenne/CayenneSupport.java @@ -0,0 +1,50 @@ +package org.pagan.quarkus.cayenne; + +import io.agroal.api.AgroalDataSource; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.configuration.server.ServerRuntime; +import org.apache.cayenne.configuration.server.ServerRuntimeBuilder; +import org.apache.cayenne.log.JdbcEventLogger; +import org.apache.cayenne.log.NoopJdbcEventLogger; + +/** + * + * @author Edward M. Kagan + */ +@Singleton +public class CayenneSupport { + + @Inject + AgroalDataSource dataSource; + + ServerRuntime cayenneRuntime; + + public CayenneSupport() { + System.out.println("Cayenne support now"); + } + + void initialize(CayenneConfig config) { + ServerRuntimeBuilder builder = ServerRuntime.builder() + .addConfig(config.config) + .dataSource(dataSource); + + if (!config.log) { + builder = builder.addModule(binder -> binder + .bind(JdbcEventLogger.class) + .to(NoopJdbcEventLogger.class)); + } + + cayenneRuntime = builder.build(); + } + + public void shutdown() { + cayenneRuntime.shutdown(); + } + + public ObjectContext context() { + return cayenneRuntime.newContext(); + } + +} diff --git a/demo/.dockerignore b/demo/.dockerignore new file mode 100644 index 0000000..b86c7ac --- /dev/null +++ b/demo/.dockerignore @@ -0,0 +1,4 @@ +* +!target/*-runner +!target/*-runner.jar +!target/lib/* \ No newline at end of file diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 0000000..087a183 --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1,35 @@ +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties \ No newline at end of file diff --git a/demo/.mvn/wrapper/MavenWrapperDownloader.java b/demo/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..b901097 --- /dev/null +++ b/demo/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/demo/.mvn/wrapper/maven-wrapper.jar b/demo/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..2cc7d4a Binary files /dev/null and b/demo/.mvn/wrapper/maven-wrapper.jar differ diff --git a/demo/.mvn/wrapper/maven-wrapper.properties b/demo/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/demo/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000..e8693bd --- /dev/null +++ b/demo/README.md @@ -0,0 +1,30 @@ +# security-jwt-quickstart project + +This project uses Quarkus, the Supersonic Subatomic Java Framework. + +If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . + +## Running the application in dev mode + +You can run your application in dev mode that enables live coding using: +``` +./mvnw quarkus:dev +``` + +## Packaging and running the application + +The application can be packaged using `./mvnw package`. +It produces the `security-jwt-quickstart-1.0-SNAPSHOT-runner.jar` file in the `/target` directory. +Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/lib` directory. + +The application is now runnable using `java -jar target/security-jwt-quickstart-1.0-SNAPSHOT-runner.jar`. + +## Creating a native executable + +You can create a native executable using: `./mvnw package -Pnative`. + +Or, if you don't have GraalVM installed, you can run the native executable build in a container using: `./mvnw package -Pnative -Dquarkus.native.container-build=true`. + +You can then execute your native executable with: `./target/security-jwt-quickstart-1.0-SNAPSHOT-runner` + +If you want to learn more about building native executables, please consult https://quarkus.io/guides/building-native-image-guide. \ No newline at end of file diff --git a/demo/mvnw b/demo/mvnw new file mode 100755 index 0000000..41c0f0c --- /dev/null +++ b/demo/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/demo/mvnw.cmd b/demo/mvnw.cmd new file mode 100644 index 0000000..8611571 --- /dev/null +++ b/demo/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/demo/pom.xml b/demo/pom.xml new file mode 100644 index 0000000..787ab55 --- /dev/null +++ b/demo/pom.xml @@ -0,0 +1,126 @@ + + + 4.0.0 + + + org.pagan.quarkus + extensions + 1.0-SNAPSHOT + + + demo + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + io.quarkus + quarkus-resteasy + + + org.pagan.quarkus + jedis + 1.0-SNAPSHOT + + + org.pagan.quarkus + janitor + 1.0-SNAPSHOT + + + org.pagan.quarkus + cayenne + 1.0-SNAPSHOT + + + io.quarkus + quarkus-jdbc-postgresql + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + build + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + native + + + + diff --git a/demo/src/main/docker/Dockerfile.jvm b/demo/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..e5a2cbd --- /dev/null +++ b/demo/src/main/docker/Dockerfile.jvm @@ -0,0 +1,47 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the docker image run: +# +# mvn package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/security-jwt-quickstart-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/security-jwt-quickstart-jvm +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 + +ARG JAVA_PACKAGE=java-11-openjdk-headless +ARG RUN_JAVA_VERSION=1.3.5 + +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' + +# Install java and the run-java script +# Also set up permissions for user `1001` +RUN microdnf install openssl curl ca-certificates ${JAVA_PACKAGE} \ + && microdnf update \ + && microdnf clean all \ + && mkdir /deployments \ + && chown 1001 /deployments \ + && chmod "g+rwX" /deployments \ + && chown 1001:root /deployments \ + && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ + && chown 1001 /deployments/run-java.sh \ + && chmod 540 /deployments/run-java.sh \ + && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security + +# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" + +COPY target/lib/* /deployments/lib/ +COPY target/*-runner.jar /deployments/app.jar + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT [ "/deployments/run-java.sh" ] \ No newline at end of file diff --git a/demo/src/main/docker/Dockerfile.native b/demo/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..d7e28ca --- /dev/null +++ b/demo/src/main/docker/Dockerfile.native @@ -0,0 +1,30 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode +# +# Before building the docker image run: +# +# mvn package -Pnative -Dquarkus.native.container-build=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/security-jwt-quickstart . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/security-jwt-quickstart +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1 +WORKDIR /work/ +COPY target/*-runner /work/application + +# set up permissions for user `1001` +RUN chmod 775 /work /work/application \ + && chown -R 1001 /work \ + && chmod -R "g+rwX" /work \ + && chown -R 1001:root /work + +EXPOSE 8080 +USER 1001 + +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/demo/src/main/java/org/pagan/janitor/LoginResource.java b/demo/src/main/java/org/pagan/janitor/LoginResource.java new file mode 100644 index 0000000..879e682 --- /dev/null +++ b/demo/src/main/java/org/pagan/janitor/LoginResource.java @@ -0,0 +1,39 @@ +package org.pagan.janitor; + +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.MediaType; +import org.pagan.janitor.cache.SessionCacheImpl; +import org.pagan.janitor.cache.SessionInfo; + +@Path("/auth") +@RequestScoped +public class LoginResource { + + @Inject + SessionInfo session; + + @Inject + SessionCacheImpl sessionCache; + + @POST + @Path("/login") + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed({"anonymous"}) + public Response login (@FormParam("login") String login, @FormParam("pass") String pass) { +// System.out.println(login + " : " + pass); + if (login.equals("demo") && pass.equals("demo")) { + SessionInfo si = new SessionInfo().role("admin");//.name("demo"); + return sessionCache.loginSuccessResponse(si); + } else { + return Response.status(Response.Status.FORBIDDEN).build(); + } + } + +} \ No newline at end of file diff --git a/demo/src/main/java/org/pagan/janitor/TokenSecuredResource.java b/demo/src/main/java/org/pagan/janitor/TokenSecuredResource.java new file mode 100644 index 0000000..0c93b95 --- /dev/null +++ b/demo/src/main/java/org/pagan/janitor/TokenSecuredResource.java @@ -0,0 +1,123 @@ +package org.pagan.janitor; + + +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import org.pagan.janitor.cache.SessionInfo; + +//import org.eclipse.microprofile.jwt.JsonWebToken; + +/** + * Version 2 of the TokenSecuredResource + */ +@Path("/test") +@RequestScoped +public class TokenSecuredResource { + + +// @Inject +// CayenneSupport cayenne; +// +// @Inject +// JedisSupport jedis; + + @Inject + SessionInfo session; + + @GET + @Produces(MediaType.TEXT_PLAIN) + @PermitAll + public String get () { +// System.out.println(session.role()); + return "permit-all"; + } + + + + @GET + @Path("/admin") + @Produces(MediaType.TEXT_PLAIN) + @RolesAllowed({"devel", "admin", "user"}) + public String init() { + long start = System.currentTimeMillis(); +// StringBuilder sb = new StringBuilder(); +// +// for (int i = 0; i < 20; i++) { +// try (Jedis jedisContext = jedis.context()) { +// +// HashMap info = new HashMap() { +// { +// put("created_at", "100100"); +// put("expires_at", "200200"); +// put("role", "admin"); +// put("last_action_at", String.valueOf(System.currentTimeMillis())); +// } +// }; +// jedisContext.hset("", info); +// } +// } +// +// ObjectContext cayenneContext = cayenne.context(); +// long count = ObjectSelect.query(SecUser.class).selectCount(cayenneContext); +//// System.out.println("count = " + count); +// System.out.println(System.currentTimeMillis()); +// ObjectSelect.query(SecUser.class).iterate(cayenneContext, (SecUser a) -> { +////// System.out.println(a.getFirstName() + " " + a.getLastName()); +// }); +//// try (ResultBatchIterator batchIterator = ObjectSelect.query(SecUser.class).batchIterator(cayenneContext, 100)) { +//// for (List list : batchIterator) { +//// for (SecUser a : list) { +////// sb.append(a.getFirstName().substring(0,1) + ""); +//// for (int i = 0; i < 20; i++) { +////// sb.append(a.getLastName() + " " + a.getFirstName() + " " + a.getMiddleName() + " (" + a.getContactEmail() + ") "); +//// } +//// +////// System.out.println(a.getFirstName() + " " + a.getLastName() + " "); +//// } +//// } +//// } +//// while (batchIterator.hasNext()) { +//// List next = batchIterator.next(); +//// for (SecUser user : next) { +////// System.out.println("user = " + user.getFirstName() + ":" + user.getLastName()); +//// } +//// } +// +//// throw new UnsupportedOperationException("asd"); +//// ObjectContext newContext = serverRuntime.newContext(); + return String.valueOf(System.currentTimeMillis() - start); + } + +// @Inject +// JsonWebToken jwt; + +// @GET() +// @Path("permit-all") +// @PermitAll +// @Produces(MediaType.TEXT_PLAIN) +// public String hello(@Context SecurityContext ctx) { +// Principal caller = ctx.getUserPrincipal(); +// String name = caller == null ? "anonymous" : caller.getName(); +// String helloReply = String.format("hello + %s, isSecure: %s, authScheme: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme()); +// return helloReply; +// } +// +// @GET() +// @Path("roles-allowed") +// @RolesAllowed({"Echoer", "Subscriber"}) +// @Produces(MediaType.TEXT_PLAIN) +// public String helloRolesAllowed(@Context SecurityContext ctx) { +// Principal caller = ctx.getUserPrincipal(); +// String name = caller == null ? "anonymous" : caller.getName(); +//// boolean hasJWT = jwt.getClaimNames() != null; +//// String helloReply = String.format("hello + %s, isSecure: %s, authScheme: %s, hasJWT: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJWT); +//// return helloReply; +// return "2" +// } +} \ No newline at end of file diff --git a/demo/src/main/java/org/pagan/janitor/session/DemoCache.java b/demo/src/main/java/org/pagan/janitor/session/DemoCache.java new file mode 100644 index 0000000..2f403a7 --- /dev/null +++ b/demo/src/main/java/org/pagan/janitor/session/DemoCache.java @@ -0,0 +1,59 @@ +package org.pagan.janitor.session; + +import java.util.Map; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import org.pagan.janitor.cache.SessionCacheImpl; +import org.pagan.janitor.cache.SessionInfo; +import org.pagan.quarkus.jedis.JedisSupport; +import redis.clients.jedis.Jedis; + +/** + * + * @author Edward M. Kagan + */ +@ApplicationScoped +public class DemoCache extends SessionCacheImpl { + + @Inject + JedisSupport jedisSupport; + + @Override + public SessionInfo get(String sessionId) { + Map session; + try (Jedis jedis = jedisSupport.context()) { + session = jedis.hgetAll(sessionId); + } + return new SessionInfo() + .sessionId(sessionId) + .csrfToken(session.get("csrf")) + .role(session.get("role")) +// .name(session.get("name")) + .createdAt(Long.valueOf(session.get("created"))) + .expiresAt(Long.valueOf(session.get("expires"))); + } + + @Override + public void put(String sessionId, SessionInfo sessionInfo) { + try (Jedis jedis = jedisSupport.context()) { + jedis.hset(sessionId, "csrf", sessionInfo.csrfToken()); + jedis.hset(sessionId, "role", sessionInfo.role()); +// jedis.hset(sessionId, "name", sessionInfo.name()); + jedis.hset(sessionId, "created", String.valueOf(sessionInfo.createdAt())); + jedis.hset(sessionId, "expires", String.valueOf(sessionInfo.expiresAt())); + jedis.expire(sessionId, config.sessionLifetime.intValue()); + } + } + + @Override + public void del(String sessionId) { + try (Jedis jedis = jedisSupport.context()) { + jedis.hdel(sessionId, "csrf"); + jedis.hdel(sessionId, "role"); +// jedis.hdel(sessionId, "name"); + jedis.hdel(sessionId, "created"); + jedis.hdel(sessionId, "expires"); + } + } + +} diff --git a/demo/src/main/resources/META-INF/resources/index.html b/demo/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000..b7ebadb --- /dev/null +++ b/demo/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,19 @@ + + + + + security-jwt-quickstart - 1.0-SNAPSHOT + + + +
+
+
+
+

+ +
+ + + + \ No newline at end of file diff --git a/demo/src/main/resources/application.properties b/demo/src/main/resources/application.properties new file mode 100644 index 0000000..90590d4 --- /dev/null +++ b/demo/src/main/resources/application.properties @@ -0,0 +1,29 @@ + +quarkus.banner.enabled=false + +# Cayenne model source +quarkus.cayenne.config=cayenne-expero.xml + +# Redis connection config +quarkus.jedis.shards=redis://localhost:6379 +quarkus.jedis.poolMax=40 +quarkus.jedis.maxWait=100500 +quarkus.jedis.log=true + +# Cayenne Data Source config +#quarkus.janitor.session-lifetime=3600 + +# Data Source config +quarkus.datasource.transactions=disabled +quarkus.datasource.initial-size=2 +quarkus.datasource.min-size=10 +quarkus.datasource.max-size=40 +quarkus.datasource.acquisition-timeout=10 +quarkus.datasource.url=jdbc:postgresql://localhost:5432/sqs_development +quarkus.datasource.driver=org.postgresql.Driver +quarkus.datasource.username=sqs_development +quarkus.datasource.password=sqs_development + +# quarkus +# quarkus.liquibase.change-log=db/liquibase-changelog-master.xml + diff --git a/demo/src/test/java/org/acme/security/jwt/NativeTokenSecuredResourceIT.java b/demo/src/test/java/org/acme/security/jwt/NativeTokenSecuredResourceIT.java new file mode 100644 index 0000000..7ec5a1b --- /dev/null +++ b/demo/src/test/java/org/acme/security/jwt/NativeTokenSecuredResourceIT.java @@ -0,0 +1,9 @@ +//package org.acme.security.jwt; +// +//import io.quarkus.test.junit.NativeImageTest; +// +//@NativeImageTest +//public class NativeTokenSecuredResourceIT extends TokenSecuredResourceTest { +// +// // Execute the same tests but in native mode. +//} \ No newline at end of file diff --git a/demo/src/test/java/org/acme/security/jwt/TokenSecuredResourceTest.java b/demo/src/test/java/org/acme/security/jwt/TokenSecuredResourceTest.java new file mode 100644 index 0000000..77c5ecd --- /dev/null +++ b/demo/src/test/java/org/acme/security/jwt/TokenSecuredResourceTest.java @@ -0,0 +1,21 @@ +//package org.acme.security.jwt; +// +//import io.quarkus.test.junit.QuarkusTest; +//import org.junit.jupiter.api.Test; +// +//import static io.restassured.RestAssured.given; +//import static org.hamcrest.CoreMatchers.is; +// +//@QuarkusTest +//public class TokenSecuredResourceTest { +// +// @Test +// public void testHelloEndpoint() { +// given() +// .when().get("/secured") +// .then() +// .statusCode(200) +// .body(is("hello")); +// } +// +//} \ No newline at end of file diff --git a/janitor/deployment/.classpath b/janitor/deployment/.classpath new file mode 100644 index 0000000..8131be0 --- /dev/null +++ b/janitor/deployment/.classpath @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/janitor/deployment/.project b/janitor/deployment/.project new file mode 100644 index 0000000..45ea084 --- /dev/null +++ b/janitor/deployment/.project @@ -0,0 +1,23 @@ + + + quarkus-smallrye-jwt-deployment + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/janitor/deployment/.settings/org.eclipse.core.resources.prefs b/janitor/deployment/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..cdfe4f1 --- /dev/null +++ b/janitor/deployment/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/janitor/deployment/.settings/org.eclipse.jdt.core.prefs b/janitor/deployment/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..b8947ec --- /dev/null +++ b/janitor/deployment/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/janitor/deployment/.settings/org.eclipse.m2e.core.prefs b/janitor/deployment/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/janitor/deployment/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/janitor/deployment/pom.xml b/janitor/deployment/pom.xml new file mode 100644 index 0000000..bd463d3 --- /dev/null +++ b/janitor/deployment/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + + org.pagan.quarkus + janitor-parent + 1.0-SNAPSHOT + + + janitor-deployment + ${project.artifactId} + + + + io.quarkus + quarkus-security-deployment + ${quarkus.platform.version} + + + io.quarkus + quarkus-vertx-web-deployment + ${quarkus.platform.version} + + + io.quarkus + quarkus-arc-deployment + ${quarkus.platform.version} + + + org.pagan.quarkus + janitor + 1.0-SNAPSHOT + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + + + diff --git a/janitor/deployment/src/main/java/pagan/janitor/JanitorProcessor.java b/janitor/deployment/src/main/java/pagan/janitor/JanitorProcessor.java new file mode 100644 index 0000000..526e2fa --- /dev/null +++ b/janitor/deployment/src/main/java/pagan/janitor/JanitorProcessor.java @@ -0,0 +1,54 @@ +package pagan.janitor; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import org.pagan.janitor.JanitorConfig; +import org.pagan.janitor.JanitorRecorder; +import org.pagan.janitor.cache.SessionCacheConfig; +import org.pagan.janitor.cache.SessionCacheImpl; +import org.pagan.janitor.cache.SessionInfo; +import org.pagan.janitor.security.JanitorAuthMechanism; +import org.pagan.janitor.security.JanitorPrincipalProducer; +import org.pagan.janitor.security.JanitorIdentityProvider; + +/** + * @author Edward M. Kagan + * <kaganem@2pm.tech> + */ +class JanitorProcessor { + + JanitorConfig config; + + @BuildStep + void registerAdditionalBeans(BuildProducer additionalBeans) { + AdditionalBeanBuildItem.Builder unremovable + = AdditionalBeanBuildItem.builder().setUnremovable(); + unremovable.addBeanClass(JanitorIdentityProvider.class); + unremovable.addBeanClass(JanitorAuthMechanism.class); + unremovable.addBeanClass(SessionInfo.class); + unremovable.addBeanClass(SessionCacheConfig.class); + additionalBeans.produce(unremovable.build()); + AdditionalBeanBuildItem.Builder removable = AdditionalBeanBuildItem.builder(); + removable.addBeanClass(SessionCacheImpl.class); + removable.addBeanClass(JanitorPrincipalProducer.class); + additionalBeans.produce(removable.build()); + } + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem("janitor"); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void configureCache(JanitorRecorder recorder, BeanContainerBuildItem container) { + recorder.configureAuthMechanism(container.getValue(), config); + } + + +} diff --git a/janitor/pom.xml b/janitor/pom.xml new file mode 100644 index 0000000..bf41d26 --- /dev/null +++ b/janitor/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + org.pagan.quarkus + extensions + 1.0-SNAPSHOT + + + janitor-parent + ${project.artifactId} + pom + + + deployment + runtime + + + \ No newline at end of file diff --git a/janitor/runtime/.classpath b/janitor/runtime/.classpath new file mode 100644 index 0000000..5e8a55f --- /dev/null +++ b/janitor/runtime/.classpath @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/janitor/runtime/.project b/janitor/runtime/.project new file mode 100644 index 0000000..0b955f9 --- /dev/null +++ b/janitor/runtime/.project @@ -0,0 +1,23 @@ + + + quarkus-smallrye-jwt + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/janitor/runtime/.settings/org.eclipse.core.resources.prefs b/janitor/runtime/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..e9441bb --- /dev/null +++ b/janitor/runtime/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding/=UTF-8 diff --git a/janitor/runtime/.settings/org.eclipse.jdt.core.prefs b/janitor/runtime/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..b8947ec --- /dev/null +++ b/janitor/runtime/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/janitor/runtime/.settings/org.eclipse.m2e.core.prefs b/janitor/runtime/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/janitor/runtime/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/janitor/runtime/pom.xml b/janitor/runtime/pom.xml new file mode 100644 index 0000000..551fb24 --- /dev/null +++ b/janitor/runtime/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + + org.pagan.quarkus + janitor-parent + 1.0-SNAPSHOT + + + janitor + ${project.artifactId} + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-core + ${quarkus.platform.version} + + + io.quarkus + quarkus-vertx-web + ${quarkus.platform.version} + + + io.quarkus + quarkus-security + ${quarkus.platform.version} + + + + + org.pagan.quarkus + cayenne + 1.0-SNAPSHOT + + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.platform.version} + + + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + + + diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/JanitorConfig.java b/janitor/runtime/src/main/java/org/pagan/janitor/JanitorConfig.java new file mode 100644 index 0000000..dec231b --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/JanitorConfig.java @@ -0,0 +1,79 @@ +package org.pagan.janitor; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * @author Edward M. Kagan + * <kaganem@2pm.tech> + */ + @ConfigRoot(name = "janitor") +public class JanitorConfig { + + /** + * Session cookie name + */ + @ConfigItem(defaultValue = "session") + public String cookieName; + + /** + * CSRF protection enabled + */ + @ConfigItem(defaultValue = "false") + public boolean csrfDisabled; + + /** + * CSRF token updated on each request + */ + @ConfigItem(defaultValue = "false") + public boolean oneTimeCsrfToken; + + /** + * CSRF token updates after each POST, DELETE, UPDATE and PUT request + */ + @ConfigItem(defaultValue = "true") + public boolean oneCommitCsrfToken; + + /** + * CSRF token updates randomly on some requests based + */ + @ConfigItem(defaultValue = "false") + public boolean randomUpdateCsrf; + + /** + * Is last accessed path stored in session + */ + @ConfigItem(defaultValue = "true") + public boolean trackLastPath; + + /** + * Is last access time stored in session + */ + @ConfigItem(defaultValue = "true") + public boolean trackLastAccessTime; + + /** + * If true session expiration time will be extended on each access + */ + @ConfigItem(defaultValue = "true") + public boolean extendSessionOnAccess; + + /** + * Session lifetime in seconds + */ + @ConfigItem(defaultValue = "60") + public Long sessionLifetime; + + /** + * Configures header or cookie name + */ + @ConfigItem(defaultValue = "X-CSRF-TOKEN") + public String csrfName; + + /** + * If true janitor put csrf token in cookie + */ + @ConfigItem(defaultValue = "false") + public boolean csrfInCookie; + +} diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/JanitorRecorder.java b/janitor/runtime/src/main/java/org/pagan/janitor/JanitorRecorder.java new file mode 100644 index 0000000..f4c67fb --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/JanitorRecorder.java @@ -0,0 +1,35 @@ +package org.pagan.janitor; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.runtime.annotations.Recorder; +import org.pagan.janitor.cache.SessionCacheConfig; +//import org.pagan.janitor.cache.CayenneSessionCache; +import org.pagan.janitor.security.JanitorAuthMechanism; + +/** + * @author Edward M. Kagan + * <kaganem@2pm.tech> + */ +@Recorder +public class JanitorRecorder { + +// public void configureCache( +// BeanContainer container, +// int sessionLifeTime, +// String coockieName) { +// CayenneSessionCache sessionCache = container.instance(CayenneSessionCache.class); +// sessionCache.setSessionLifeTime(sessionLifeTime); +// sessionCache.setCoockieName(coockieName); +// } + + public void configureAuthMechanism(BeanContainer container, JanitorConfig config) { + + SessionCacheConfig cacheConfig = container.instance(SessionCacheConfig.class); + cacheConfig.setConfig(config); + + JanitorAuthMechanism authMechanism = container.instance(JanitorAuthMechanism.class); + authMechanism.setConfig(config); + + } + +} diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionCache.java b/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionCache.java new file mode 100644 index 0000000..e21746f --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionCache.java @@ -0,0 +1,15 @@ +package org.pagan.janitor.cache; + +import javax.ws.rs.core.Response; + +/** + * + * @author pagan + */ +public interface SessionCache { + + public SessionInfo get(String sessionId); + public void put(String sessionId, SessionInfo sessionInfo); + public void del(String sessionId); + public Response loginSuccessResponse(SessionInfo sessionInfo); +} diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionCacheConfig.java b/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionCacheConfig.java new file mode 100644 index 0000000..6322b8a --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionCacheConfig.java @@ -0,0 +1,25 @@ +package org.pagan.janitor.cache; + +import javax.inject.Singleton; +import org.pagan.janitor.JanitorConfig; + +/** + * + * @author Edward M. Kagan + */ +@Singleton +public class SessionCacheConfig { + + public String cookieName; + public String csrfName; + public boolean csrfInCookie; + public Long sessionLifetime; + + public void setConfig(JanitorConfig config) { + this.cookieName = config.cookieName; + this.csrfName = config.csrfName; + this.csrfInCookie = config.csrfInCookie; + this.sessionLifetime = config.sessionLifetime; + } + +} diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionCacheImpl.java b/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionCacheImpl.java new file mode 100644 index 0000000..af08c17 --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionCacheImpl.java @@ -0,0 +1,77 @@ +package org.pagan.janitor.cache; + +import io.quarkus.arc.DefaultBean; +import javax.ws.rs.core.Response; +import java.util.HashMap; +import java.util.UUID; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response.ResponseBuilder; + +/** + * + * @author Edward M. Kagan + */ +@DefaultBean +@ApplicationScoped +public class SessionCacheImpl implements SessionCache { + + @Inject + protected SessionCacheConfig config; + + HashMap sessions; + + public SessionCacheImpl() { + this.sessions = new HashMap(); + } + + @Override + public SessionInfo get(String sessionId) { + System.out.println(">>> get"); + for (String si : this.sessions.keySet()) { + System.out.println(si + " > " + this.sessions.get(si)); + } + return this.sessions.get(sessionId); + } + + @Override + public void put(String sessionId, SessionInfo sessionInfo) { + System.out.println(">>> put"); + this.sessions.put(sessionId, sessionInfo); + for (String si : this.sessions.keySet()) { + System.out.println(si + " > " + this.sessions.get(si)); + } + } + + @Override + public void del(String sessionId) { + this.sessions.remove(sessionId); + } + + @Override + public Response loginSuccessResponse(SessionInfo sessionInfo) { +// System.out.println("config = " + config); + final String sessionId = UUID.randomUUID().toString(); + final String csrfToken = UUID.randomUUID().toString(); + final long createdAt = System.currentTimeMillis(); + final long expiresAt = createdAt + config.sessionLifetime * 1000 * 1000; + sessionInfo.createdAt(createdAt); + sessionInfo.expiresAt(expiresAt); + sessionInfo.sessionId(sessionId); + sessionInfo.csrfToken(csrfToken); + put(sessionId, sessionInfo); + ResponseBuilder builder = Response.ok().cookie(new NewCookie(config.cookieName, + sessionId, "/", null, null, config.sessionLifetime.intValue(), + false, true)); + if (config.csrfInCookie) { + builder.cookie(new NewCookie(config.csrfName, + sessionInfo.csrfToken(), "/", null, null, config.sessionLifetime.intValue(), + false, true)); + } else { + builder.header(config.csrfName, sessionInfo.csrfToken()); + } + return builder.build(); + } + +} diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionInfo.java b/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionInfo.java new file mode 100644 index 0000000..52fa356 --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/cache/SessionInfo.java @@ -0,0 +1,79 @@ +package org.pagan.janitor.cache; + +import java.security.Principal; + +public class SessionInfo implements Principal { + + public static final SessionInfo ANONYMOUS = new SessionInfo().role("anonymous"); + private String sessionId; + private long createdAt; + private long expiresAt; + private String name; + private String role; + private String csrfToken; + + public SessionInfo() {} + + public String sessionId() { + return sessionId; + } + + public SessionInfo sessionId(String sessionId) { + this.sessionId = sessionId; + return this; + } + + public long createdAt() { + return createdAt; + } + + public SessionInfo createdAt(long createdAt) { + this.createdAt = createdAt; + return this; + } + + public long expiresAt() { + return expiresAt; + } + + public SessionInfo expiresAt(long expiresAt) { + this.expiresAt = expiresAt; + return this; + } + + public SessionInfo name(String name) { + this.name = name; + return this; + } + + public String name () { + return getName(); + } + + public String role() { + return role; + } + + public SessionInfo role(String role) { + this.role = role; + return this; + } + + public String csrfToken() { + return csrfToken; + } + + public SessionInfo csrfToken(String csrfToken) { + this.csrfToken = csrfToken; + return this; + } + + @Override + public String getName() { + return this.name; + } + + + + +} diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorAuthMechanism.java b/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorAuthMechanism.java new file mode 100644 index 0000000..120cd16 --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorAuthMechanism.java @@ -0,0 +1,94 @@ +package org.pagan.janitor.security; + +import io.netty.handler.codec.http.HttpResponseStatus; +import io.quarkus.security.identity.IdentityProviderManager; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.identity.request.AuthenticationRequest; +import io.quarkus.vertx.http.runtime.security.ChallengeData; +import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism; +import io.quarkus.vertx.http.runtime.security.HttpCredentialTransport; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.http.Cookie; +import io.vertx.ext.web.RoutingContext; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.enterprise.context.ApplicationScoped; +import org.pagan.janitor.JanitorConfig; + +/** + * @author Edward M. Kagan + * <kaganem@2pm.tech> + */ +@ApplicationScoped +public class JanitorAuthMechanism implements HttpAuthenticationMechanism { + + JanitorConfig config; + + public void setConfig(JanitorConfig config) { + this.config = config; + } + + @Override + public CompletionStage authenticate(RoutingContext context, IdentityProviderManager janitorIdentityProvider) { + Cookie cookie = context.getCookie(config.cookieName); + if (cookie != null) { + final HttpServerRequest request = context.request(); + final HttpMethod method = request.method(); + final String path = request.path(); + final String origin = request.getHeader("Origin"); + final HttpServerResponse response = context.response(); + String csrfToken = null; + String csrfTarget = config.csrfName; + if (config.csrfInCookie) { + csrfTarget = config.csrfName; + Cookie csrfCookie = context.getCookie(csrfTarget); + if (csrfCookie != null) { + csrfToken = csrfCookie.getValue(); + } + } else { + csrfToken = request.getHeader(csrfTarget); + } + return janitorIdentityProvider.authenticate(new JanitorAuthenticationRequest(cookie.getValue(), method, csrfToken, path, origin, response, config.csrfInCookie, csrfTarget)); + } else { + return janitorIdentityProvider.authenticate(JanitorAuthenticationRequest.REJECTOR); + } + } + + @Override + public CompletionStage getChallenge(RoutingContext rc) { + System.out.println("getChallenge"); + return CompletableFuture.completedFuture( + new ChallengeData( + HttpResponseStatus.UNAUTHORIZED.code(), "", "" + ) + ); + } + + @Override + public CompletionStage sendChallenge(RoutingContext context) { + System.out.println("sendChallenge"); + Cookie cookie = context.getCookie(config.cookieName); + if (cookie != null) { + cookie.setMaxAge(0); + } + context.response().setStatusCode(HttpResponseStatus.UNAUTHORIZED.code()); + return CompletableFuture.completedFuture(false); + } + + @Override + public Set> getCredentialTypes() { + System.out.println("getCredentialTypes"); + return Collections.singleton(JanitorAuthenticationRequest.class); + } + + @Override + public HttpCredentialTransport getCredentialTransport() { + System.out.println("getCredentialTransport"); + return new HttpCredentialTransport(HttpCredentialTransport.Type.COOKIE, config.cookieName); + } + +} diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorAuthenticationRequest.java b/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorAuthenticationRequest.java new file mode 100644 index 0000000..2060fa6 --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorAuthenticationRequest.java @@ -0,0 +1,76 @@ +package org.pagan.janitor.security; + +import io.quarkus.security.identity.request.AuthenticationRequest; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServerResponse; + +/** + * @author Edward M. Kagan + * <kaganem@2pm.tech> + */ +public class JanitorAuthenticationRequest implements AuthenticationRequest { + + public static final JanitorAuthenticationRequest REJECTOR = new JanitorAuthenticationRequest( + null, null, null, null, null, null, true, null + ); + + private final String sessionId; + private final HttpMethod method; + private final String csrfToken; + private final String path; + private final HttpServerResponse response; + private final String origin; + private final boolean csrfCookie; + private final String csrfTarget; + + public JanitorAuthenticationRequest(String sessionId, + HttpMethod method, + String csrfToken, + String path, + String origin, + HttpServerResponse response, + boolean csrfCookie, + String csrfTarget) { + this.sessionId = sessionId; + this.method = method; + this.csrfToken = csrfToken; + this.path = path; + this.origin = origin; + this.response = response; + this.csrfCookie = csrfCookie; + this.csrfTarget = csrfTarget; + } + + public String getSessionId() { + return sessionId; + } + + public HttpMethod getMethod() { + return method; + } + + public String getCsrfToken() { + return csrfToken; + } + + public String getPath() { + return path; + } + + public String getOrigin() { + return origin; + } + + public boolean isCsrfCookie() { + return csrfCookie; + } + + public String getCsrfTarget() { + return csrfTarget; + } + + void putHeader(String header, String value) { + response.putHeader(header, value); + } + +} diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorIdentityProvider.java b/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorIdentityProvider.java new file mode 100644 index 0000000..bf9d32d --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorIdentityProvider.java @@ -0,0 +1,112 @@ +package org.pagan.janitor.security; + +import io.quarkus.security.AuthenticationFailedException; +import io.quarkus.security.identity.AuthenticationRequestContext; +import io.quarkus.security.identity.IdentityProvider; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.runtime.QuarkusSecurityIdentity; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.logging.Logger; +import io.vertx.core.logging.LoggerFactory; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import org.pagan.janitor.cache.SessionCache; +import org.pagan.janitor.cache.SessionInfo; +//import org.pagan.janitor.security.JanitorPrincipalProducer.NullSessionToken; + +/** + * @author Edward M. Kagan + * <kaganem@2pm.tech> + */ +@ApplicationScoped +public class JanitorIdentityProvider implements IdentityProvider { + + private static final Logger LOG = LoggerFactory.getLogger(JanitorIdentityProvider.class.getName()); + + @Inject + SessionCache sessionCache; + + @Override + public Class getRequestType() { + return JanitorAuthenticationRequest.class; + } + + @Override + public CompletionStage authenticate( + JanitorAuthenticationRequest request, + AuthenticationRequestContext context) { + if (request.getMethod() == null) { + return anonymous(); + } + + final HttpMethod method = request.getMethod(); + final String sessionId = request.getSessionId(); + final String csrfToken = request.getCsrfToken(); + final String path = request.getPath(); + + if (request.getOrigin() != null && request.getOrigin().length() > 0) + { +// request.putHeader("Access-Control-Allow-Origin", request.getOrigin()); +// request.putHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE, PATCH, HEAD"); +// request.putHeader("Access-Control-Allow-Credentials", "true"); +// request.putHeader("Access-Control-Max-Age", "1209600"); +// request.putHeader("Access-Control-Expose-Headers", "X-CSRF-TOKEN, X-CSRF-ERROR"); +// request.putHeader("Access-Control-Allow-Headers", "origin, accept, authorization, content-type, x-requested-with, x-csrf-token, x-csrf-error"); + } + + if (method == HttpMethod.OPTIONS) { + LOG.debug("method = OPTIONS"); + return anonymous(); + } + + if (sessionId == null) { + LOG.debug("sessionId = null"); + return anonymous(); + } + + SessionInfo sessionInfo = sessionCache.get(sessionId); + LOG.debug("sessionInfo = " + sessionInfo); + + if (sessionInfo == null) { + LOG.debug("session info not found in session storage"); + return anonymous(); + } + + if (method != HttpMethod.GET && method != HttpMethod.HEAD) { + LOG.debug("path = " + path); + if (!path.equals("/api/auth/") && !path.equals("/api/auth") ) { + if (csrfToken == null) { + LOG.warn("csrfToken is null"); + return failed(); + } + if (!sessionInfo.csrfToken().equals(csrfToken)) { + LOG.error("bad csrfToken"); + return failed(); + } + } + } + + return principal(sessionInfo); + } + + private CompletionStage anonymous() { + return principal(SessionInfo.ANONYMOUS); + } + + private CompletionStage principal(SessionInfo sessionInfo) { + return CompletableFuture.completedFuture( + QuarkusSecurityIdentity.builder().setPrincipal(sessionInfo) + .addRole(sessionInfo.role()) + .build() + ); + } + + private CompletionStage failed() { + CompletableFuture cf = new CompletableFuture(); + cf.completeExceptionally(new AuthenticationFailedException()); + return cf; + } + +} diff --git a/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorPrincipalProducer.java b/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorPrincipalProducer.java new file mode 100644 index 0000000..5251732 --- /dev/null +++ b/janitor/runtime/src/main/java/org/pagan/janitor/security/JanitorPrincipalProducer.java @@ -0,0 +1,76 @@ +package org.pagan.janitor.security; + +import io.quarkus.security.identity.SecurityIdentity; +import io.vertx.core.logging.Logger; +import io.vertx.core.logging.LoggerFactory; +import javax.annotation.Priority; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import org.pagan.janitor.cache.SessionInfo; + +/** + * @author Edward M. Kagan + * <kaganem@2pm.tech> + */ +@Priority(1) +@Alternative +@RequestScoped +public class JanitorPrincipalProducer { + + private static final Logger LOG + = LoggerFactory.getLogger(JanitorPrincipalProducer.class.getName()); + + @Inject + SecurityIdentity identity; + + @Produces + @RequestScoped + SessionInfo currentSessionPrincipalOrNull() { + LOG.debug("currentSessionPrincipalOrNull"); + if (identity.getPrincipal() instanceof SessionInfo) { + return (SessionInfo) identity.getPrincipal(); + } + throw new IllegalStateException("Current principal " + + identity.getPrincipal() + " is not a Coockie token"); + } + +// public static class NullSessionToken extends SessionInfo { +// +// private static final UnsupportedOperationException MUTATION_ALERT +// = new UnsupportedOperationException( +// "Null session does not support mutations, dummy" +// ); +// +// @Override +// protected void setCsrfToken(String csrfToken) { +// throw MUTATION_ALERT; +// } +// +// @Override +// protected void setRoleName(String userRoleName) { +// throw MUTATION_ALERT; +// } +// +// @Override +// protected void setUserId(long userId) { +// throw MUTATION_ALERT; +// } +// +// @Override +// protected void setExpiresAt(long sessionExpiresAt) { +// throw MUTATION_ALERT; +// } +// +// @Override +// protected void setCreatedAt(long sessionCreatedAt) { +// throw MUTATION_ALERT; +// } +// +// @Override +// protected void setSessionId(String sessionId) { +// throw MUTATION_ALERT; +// } +// } +} diff --git a/jedis/.gitignore b/jedis/.gitignore new file mode 100644 index 0000000..56c447b --- /dev/null +++ b/jedis/.gitignore @@ -0,0 +1,124 @@ + +# Created by https://www.gitignore.io/api/maven,java,intellij +# Edit at https://www.gitignore.io/?templates=maven,java,intellij + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# JetBrains templates +**___jb_tmp___ + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# End of https://www.gitignore.io/api/maven,java,intellij diff --git a/jedis/deployment/pom.xml b/jedis/deployment/pom.xml new file mode 100644 index 0000000..e4c9a7b --- /dev/null +++ b/jedis/deployment/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + + org.pagan.quarkus + jedis-parent + 1.0-SNAPSHOT + + + jedis-deployment + ${project.artifactId} + + + + io.quarkus + quarkus-core-deployment + ${quarkus.platform.version} + + + io.quarkus + quarkus-arc-deployment + ${quarkus.platform.version} + + + io.quarkus + quarkus-agroal-deployment + ${quarkus.platform.version} + + + org.pagan.quarkus + jedis + 1.0-SNAPSHOT + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + + + \ No newline at end of file diff --git a/jedis/deployment/src/main/java/org/pagan/quarkus/jedis/JedisProccessor.java b/jedis/deployment/src/main/java/org/pagan/quarkus/jedis/JedisProccessor.java new file mode 100644 index 0000000..b5fb652 --- /dev/null +++ b/jedis/deployment/src/main/java/org/pagan/quarkus/jedis/JedisProccessor.java @@ -0,0 +1,58 @@ +package org.pagan.quarkus.jedis; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.ServiceStartBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; + +/** + * + * @author Edward M. Kagan + */ +public class JedisProccessor { + + private JedisConfig config; + + @BuildStep + FeatureBuildItem feature() { + System.out.println("JedisProccessor - feature"); + return new FeatureBuildItem("jedis"); + } + + @BuildStep + AdditionalBeanBuildItem beans() { + System.out.println("JedisProccessor - beans"); + return AdditionalBeanBuildItem.unremovableOf(JedisSupport.class); + } + + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep + void build(JedisRecorder recorder, BuildProducer serviceStart, BeanContainerBuildItem beanContainer, ShutdownContextBuildItem shutdownContext) { + System.out.println("JedisProccessor - build"); +// BuildProducer reflectiveClassBuildItemBuildProducer) { +// reflectiveClassBuildItemBuildProducer.produce(new ReflectiveClassBuildItem(false, false, BaseGenericObjectPool.class.getName())); +// reflectiveClassBuildItemBuildProducer.produce(new ReflectiveClassBuildItem(false, false, DefaultEvictionPolicy.class.getName())); + recorder.initialize(config, beanContainer.getValue(), shutdownContext); + serviceStart.produce(new ServiceStartBuildItem("jedis")); + } + + // @BuildStep +// SubstrateProxyDefinitionBuildItem httpProxies() { +// return new SubstrateProxyDefinitionBuildItem(MBeanServer.class.getName(), +// MBeanServerConnection.class.getName(), +// KeyedObjectPool.class.getName(), +// KeyedPooledObjectFactory.class.getName(), +// ObjectPool.class.getName(), +// PooledObject.class.getName(), +// PooledObjectFactory.class.getName(), +// SwallowedExceptionListener.class.getName(), +// TrackedUse.class.getName(), +// UsageTracking.class.getName()); +// } + +} diff --git a/jedis/pom.xml b/jedis/pom.xml new file mode 100644 index 0000000..461f985 --- /dev/null +++ b/jedis/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + org.pagan.quarkus + extensions + 1.0-SNAPSHOT + + + jedis-parent + ${project.artifactId} + pom + + + deployment + runtime + + + \ No newline at end of file diff --git a/jedis/runtime/pom.xml b/jedis/runtime/pom.xml new file mode 100644 index 0000000..58f22cc --- /dev/null +++ b/jedis/runtime/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + + org.pagan.quarkus + jedis-parent + 1.0-SNAPSHOT + + + jedis + ${project.artifactId} + + + + io.quarkus + quarkus-core + ${quarkus.platform.version} + + + io.quarkus + quarkus-arc + ${quarkus.platform.version} + + + io.quarkus + quarkus-agroal + ${quarkus.platform.version} + + + redis.clients + jedis + ${jedis.version} + + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.platform.version} + + + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + + + \ No newline at end of file diff --git a/jedis/runtime/src/main/java/org/pagan/quarkus/jedis/JedisConfig.java b/jedis/runtime/src/main/java/org/pagan/quarkus/jedis/JedisConfig.java new file mode 100644 index 0000000..21b6b82 --- /dev/null +++ b/jedis/runtime/src/main/java/org/pagan/quarkus/jedis/JedisConfig.java @@ -0,0 +1,38 @@ +package org.pagan.quarkus.jedis; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * + * @author Edward M. Kagan + */ +@ConfigRoot(name = "jedis", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public class JedisConfig { + + /** + * Comma-separated list of Redis-shards, with port number obviously + */ + @ConfigItem(defaultValue = "localhost:6379") + public String shards; + + /** + * Max connections pool size + */ + @ConfigItem(defaultValue = "15") + public int poolMax; + + /** + * Connection acquisition timeout + */ + @ConfigItem(defaultValue = "50") + public long maxWait; + + /** + * Will Jedis log activity + */ + @ConfigItem(defaultValue = "true") + public boolean log; + +} diff --git a/jedis/runtime/src/main/java/org/pagan/quarkus/jedis/JedisRecorder.java b/jedis/runtime/src/main/java/org/pagan/quarkus/jedis/JedisRecorder.java new file mode 100644 index 0000000..51aac17 --- /dev/null +++ b/jedis/runtime/src/main/java/org/pagan/quarkus/jedis/JedisRecorder.java @@ -0,0 +1,16 @@ +package org.pagan.quarkus.jedis; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class JedisRecorder { + + public void initialize(JedisConfig config, BeanContainer container, ShutdownContext shutdownContext) { + JedisSupport support = container.instance(JedisSupport.class); + support.initialize(config); + shutdownContext.addShutdownTask(() -> support.shutdown()); + } + +} diff --git a/jedis/runtime/src/main/java/org/pagan/quarkus/jedis/JedisSupport.java b/jedis/runtime/src/main/java/org/pagan/quarkus/jedis/JedisSupport.java new file mode 100644 index 0000000..8699acc --- /dev/null +++ b/jedis/runtime/src/main/java/org/pagan/quarkus/jedis/JedisSupport.java @@ -0,0 +1,59 @@ +package org.pagan.quarkus.jedis; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.inject.Singleton; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.JedisShardInfo; +import redis.clients.jedis.ShardedJedisPool; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.ShardedJedis; + +/** + * + * @author Edward M. Kagan + */ +@Singleton +public class JedisSupport { + + ShardedJedisPool shardedJedisPool = null; + JedisPool jedisPool = null; + + public JedisSupport() { + System.out.println("Jedis support now"); + } + + public void initialize(JedisConfig config) { + JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setTestOnBorrow(true); + poolConfig.setMaxWaitMillis(config.maxWait); + poolConfig.setMaxTotal(config.poolMax); +// poolConfig.setJmxEnabled(false); + poolConfig.setJmxEnabled(true); + if (config.shards.contains(",")) { + List jedisShards = Arrays.stream(config.shards.split(",")).map(uri -> { + return new JedisShardInfo(URI.create(uri)); + }).collect(Collectors.toList()); + this.shardedJedisPool = new ShardedJedisPool(poolConfig, jedisShards); + + } else { + this.jedisPool = new JedisPool(poolConfig, URI.create(config.shards)); + } + } + + public ShardedJedis sharedContext () { + return shardedJedisPool.getResource(); + } + + public Jedis context () { + return jedisPool.getResource(); + } + + public void shutdown() { +// redisRuntime.shutdown(); + } + +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8196076 --- /dev/null +++ b/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + org.pagan.quarkus + extensions + ${project.artifactId} + 1.0-SNAPSHOT + pom + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + cayenne + jedis + janitor + + demo + + + 3.8.1 + true + 1.8 + 1.8 + UTF-8 + UTF-8 + 1.3.1.Final + quarkus-universe-bom + io.quarkus + 1.3.1.Final + 2.22.1 + 4.1.RC2 + 3.2.0 + +