Grega Bremec 1 рік тому
батько
коміт
728b944096
38 змінених файлів з 2286 додано та 0 видалено
  1. 40 0
      finance-webapp/.gitignore
  2. 1 0
      finance-webapp/.mvn/wrapper/.gitignore
  3. 142 0
      finance-webapp/.mvn/wrapper/MavenWrapperDownloader.java
  4. 18 0
      finance-webapp/.mvn/wrapper/maven-wrapper.properties
  5. 316 0
      finance-webapp/mvnw
  6. 152 0
      finance-webapp/pom.xml
  7. 93 0
      finance-webapp/src/main/docker/Dockerfile.jvm
  8. 89 0
      finance-webapp/src/main/docker/Dockerfile.legacy-jar
  9. 27 0
      finance-webapp/src/main/docker/Dockerfile.native
  10. 30 0
      finance-webapp/src/main/docker/Dockerfile.native-micro
  11. 76 0
      finance-webapp/src/main/java/com/example/AddInvoice.java
  12. 37 0
      finance-webapp/src/main/java/com/example/CampaignResource.java
  13. 25 0
      finance-webapp/src/main/java/com/example/CampaignService.java
  14. 18 0
      finance-webapp/src/main/java/com/example/ForbiddenExceptionMapper.java
  15. 43 0
      finance-webapp/src/main/java/com/example/GlobalExceptionMapper.java
  16. 64 0
      finance-webapp/src/main/java/com/example/ShowInvoices.java
  17. 45 0
      finance-webapp/src/main/java/com/example/ShowTokens.java
  18. 6 0
      finance-webapp/src/main/java/com/example/model/Campaign.java
  19. 79 0
      finance-webapp/src/main/resources/META-INF/resources/index.html
  20. 76 0
      finance-webapp/src/main/resources/META-INF/resources/styles.css
  21. 16 0
      finance-webapp/src/main/resources/application.properties
  22. 23 0
      finance-webapp/src/main/resources/invoices.json
  23. 82 0
      finance-webapp/src/main/resources/templates/addInvoice.html
  24. 95 0
      finance-webapp/src/main/resources/templates/showInvoices.html
  25. 94 0
      finance-webapp/src/main/resources/templates/showtokens.html
  26. 80 0
      marketing-frontend/README.md
  27. 9 0
      marketing-frontend/app.js
  28. 29 0
      marketing-frontend/package.json
  29. 70 0
      marketing-frontend/src/main/webapp/app.js
  30. 59 0
      marketing-frontend/src/main/webapp/index.html
  31. 0 0
      marketing-frontend/src/main/webapp/jwt-decode.min.js
  32. 15 0
      marketing-frontend/src/main/webapp/keycloak.js
  33. 8 0
      marketing-frontend/src/main/webapp/keycloak.json
  34. 75 0
      marketing-frontend/src/main/webapp/styles.css
  35. 107 0
      marketing-restful-api/app.js
  36. 107 0
      marketing-restful-api/app.js-authz
  37. 8 0
      marketing-restful-api/keycloak.json
  38. 32 0
      marketing-restful-api/package.json

+ 40 - 0
finance-webapp/.gitignore

@@ -0,0 +1,40 @@
+#Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+release.properties
+.flattened-pom.xml
+
+# Eclipse
+.project
+.classpath
+.settings/
+bin/
+
+# IntelliJ
+.idea
+*.ipr
+*.iml
+*.iws
+
+# NetBeans
+nb-configuration.xml
+
+# Visual Studio Code
+.vscode
+.factorypath
+
+# OSX
+.DS_Store
+
+# Vim
+*.swp
+*.swo
+
+# patch
+*.orig
+*.rej
+
+# Local environment
+.env

+ 1 - 0
finance-webapp/.mvn/wrapper/.gitignore

@@ -0,0 +1 @@
+maven-wrapper.jar

+ 142 - 0
finance-webapp/.mvn/wrapper/MavenWrapperDownloader.java

@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader
+{
+    private static final String WRAPPER_VERSION = "3.1.1";
+
+    /**
+     * 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/org/apache/maven/wrapper/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();
+    }
+
+}

+ 18 - 0
finance-webapp/.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1,18 @@
+# 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.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar

+ 316 - 0
finance-webapp/mvnw

@@ -0,0 +1,316 @@
+#!/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
+#
+#    https://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 /usr/local/etc/mavenrc ] ; then
+    . /usr/local/etc/mavenrc
+  fi
+
+  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="`\\unset -f command; \\command -v 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/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.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" || rm -f "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$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 \
+  $MAVEN_DEBUG_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" \
+  "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

+ 152 - 0
finance-webapp/pom.xml

@@ -0,0 +1,152 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.example</groupId>
+  <artifactId>finance-webapp</artifactId>
+  <version>1.0.0-SNAPSHOT</version>
+  <properties>
+    <compiler-plugin.version>3.8.1</compiler-plugin.version>
+    <maven.compiler.release>11</maven.compiler.release>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+    <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
+    <quarkus.platform.version>2.14.2.Final</quarkus.platform.version>
+    <skipITs>true</skipITs>
+    <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
+  </properties>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>${quarkus.platform.group-id}</groupId>
+        <artifactId>${quarkus.platform.artifact-id}</artifactId>
+        <version>${quarkus.platform.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <dependencies>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-undertow</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-oidc</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-resteasy-qute</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-arc</artifactId>
+    </dependency>
+      <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-smallrye-jwt</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-junit5</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.googlecode.json-simple</groupId>
+      <artifactId>json-simple</artifactId>
+      <version>1.1.1</version>
+  </dependency>
+
+
+
+<dependency>
+    <groupId>io.quarkus</groupId>
+    <artifactId>quarkus-rest-client</artifactId>
+</dependency>
+<dependency>
+    <groupId>io.quarkus</groupId>
+    <artifactId>quarkus-rest-client-jackson</artifactId>
+</dependency>
+<dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-oidc-token-propagation</artifactId>
+    </dependency>
+
+
+
+
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>${quarkus.platform.group-id}</groupId>
+        <artifactId>quarkus-maven-plugin</artifactId>
+        <version>${quarkus.platform.version}</version>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>build</goal>
+              <goal>generate-code</goal>
+              <goal>generate-code-tests</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>${compiler-plugin.version}</version>
+        <configuration>
+          <compilerArgs>
+            <arg>-parameters</arg>
+          </compilerArgs>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>${surefire-plugin.version}</version>
+        <configuration>
+          <systemPropertyVariables>
+            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+            <maven.home>${maven.home}</maven.home>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <version>${surefire-plugin.version}</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+            <configuration>
+              <systemPropertyVariables>
+                <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
+                <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                <maven.home>${maven.home}</maven.home>
+              </systemPropertyVariables>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <profiles>
+    <profile>
+      <id>native</id>
+      <activation>
+        <property>
+          <name>native</name>
+        </property>
+      </activation>
+      <properties>
+        <skipITs>false</skipITs>
+        <quarkus.package.type>native</quarkus.package.type>
+      </properties>
+    </profile>
+  </profiles>
+</project>

+ 93 - 0
finance-webapp/src/main/docker/Dockerfile.jvm

@@ -0,0 +1,93 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/finance-webapp-jvm .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/finance-webapp-jvm
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5005
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/finance-webapp-jvm
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+#   in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+#   used to calculate a default maximal heap memory based on a containers restriction.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+#   of the container available memory as set here. The default is `50` which means 50%
+#   of the available memory is used as an upper boundary. You can skip this mechanism by
+#   setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+#   is used to calculate a default initial heap memory based on the maximum heap memory.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+#   of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+#   is used as the initial heap size. You can skip this mechanism by setting this value
+#   to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+#   This is used to calculate the maximum value of the initial heap memory. If used in
+#   a container without any memory constraints for the container then this option has
+#   no effect. If there is a memory constraint then `-Xms` is limited to the value set
+#   here. The default is 4096MB which means the calculated value of `-Xms` never will
+#   be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+#   when things are happening. This option, if set to true, will set
+#  `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+#    true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+#   https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+#   (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+#   (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+#   (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+#   previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+#   contain the necessary JRE command-line options to specify the required GC, which
+#   will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+#   accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.14
+
+ENV LANGUAGE='en_US:en'
+
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+

+ 89 - 0
finance-webapp/src/main/docker/Dockerfile.legacy-jar

@@ -0,0 +1,89 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dquarkus.package.type=legacy-jar
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/finance-webapp-legacy-jar .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/finance-webapp-legacy-jar
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5005
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/finance-webapp-legacy-jar
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+#   in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+#   used to calculate a default maximal heap memory based on a containers restriction.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+#   of the container available memory as set here. The default is `50` which means 50%
+#   of the available memory is used as an upper boundary. You can skip this mechanism by
+#   setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+#   is used to calculate a default initial heap memory based on the maximum heap memory.
+#   If used in a container without any memory constraints for the container then this
+#   option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+#   of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+#   is used as the initial heap size. You can skip this mechanism by setting this value
+#   to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+#   This is used to calculate the maximum value of the initial heap memory. If used in
+#   a container without any memory constraints for the container then this option has
+#   no effect. If there is a memory constraint then `-Xms` is limited to the value set
+#   here. The default is 4096MB which means the calculated value of `-Xms` never will
+#   be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+#   when things are happening. This option, if set to true, will set
+#  `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+#    true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+#   https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+#   (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+#   (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+#   (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+#   previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+#   contain the necessary JRE command-line options to specify the required GC, which
+#   will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+#   accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.14
+
+ENV LANGUAGE='en_US:en'
+
+
+COPY target/lib/* /deployments/lib/
+COPY target/*-runner.jar /deployments/quarkus-run.jar
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"

+ 27 - 0
finance-webapp/src/main/docker/Dockerfile.native

@@ -0,0 +1,27 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Pnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native -t quarkus/finance-webapp .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/finance-webapp
+#
+###
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
+WORKDIR /work/
+RUN chown 1001 /work \
+    && chmod "g+rwX" /work \
+    && chown 1001:root /work
+COPY --chown=1001:root target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

+ 30 - 0
finance-webapp/src/main/docker/Dockerfile.native-micro

@@ -0,0 +1,30 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+# It uses a micro base image, tuned for Quarkus native executables.
+# It reduces the size of the resulting container image.
+# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Pnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/finance-webapp .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/finance-webapp
+#
+###
+FROM quay.io/quarkus/quarkus-micro-image:1.0
+WORKDIR /work/
+RUN chown 1001 /work \
+    && chmod "g+rwX" /work \
+    && chown 1001:root /work
+COPY --chown=1001:root target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

+ 76 - 0
finance-webapp/src/main/java/com/example/AddInvoice.java

@@ -0,0 +1,76 @@
+package com.example;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.GET;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import io.quarkus.qute.Template;
+import io.quarkus.qute.TemplateInstance;
+
+@Path("/addinvoices")
+@RolesAllowed("finance-admin")
+public class AddInvoice {
+
+    
+    private final Template addInvoice;
+  
+
+    String jsonFileName = "invoices.json";
+    public AddInvoice(Template addInvoice) {
+        this.addInvoice = requireNonNull(addInvoice, "page is required");
+    }
+    private void addInvoice(String id, String name, String item, String date, String amount) throws FileNotFoundException, IOException, URISyntaxException, ParseException{
+        URL fileResource = this.getClass().getClassLoader().getResource( jsonFileName );
+        File jsonResourceFile = new File( fileResource.toURI() );
+        JSONArray invoicesList=(JSONArray) new JSONParser().parse( new FileReader( jsonResourceFile.toPath().toString() ) );
+        JSONObject newInvoice=new JSONObject();
+        newInvoice.put("id", id);
+        newInvoice.put("name", name);
+        newInvoice.put("item", item);
+        newInvoice.put("date", date);
+        newInvoice.put("amount", amount);
+        invoicesList.add(newInvoice);
+        
+        FileWriter fw= new FileWriter(jsonResourceFile);
+        fw.write(invoicesList.toJSONString());
+        fw.close();
+    }
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public TemplateInstance get(@QueryParam("invoiceId") String id, @QueryParam("name") String name,
+            @QueryParam("item") String item, @QueryParam("date") String date, @QueryParam("amount") String amount) {
+        
+        TemplateInstance invoiceTemplate = null;
+        try{
+            addInvoice(id,name,item,date,amount);
+        }catch(Exception e){
+            System.out.println("ERROR: " +e);
+            e.printStackTrace();
+            invoiceTemplate = addInvoice.data("message", "ERROR adding the invoice");
+        }
+        invoiceTemplate = addInvoice.data("message", "Invoice "+id+" added.");
+        return invoiceTemplate;
+    }
+
+
+
+}

+ 37 - 0
finance-webapp/src/main/java/com/example/CampaignResource.java

@@ -0,0 +1,37 @@
+package com.example;
+
+import java.util.Set;
+
+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.eclipse.microprofile.rest.client.inject.RestClient;
+
+import com.example.model.Campaign;
+
+
+
+@Path("/showcampaigns")
+public class CampaignResource {
+    @Inject
+    @RestClient
+    CampaignService campaignService;
+
+    @GET
+    @Produces(MediaType.TEXT_HTML)
+    @Path("/list")
+    public String getAll() {
+        Set<Campaign> listCampaigns=campaignService.getAll();
+        String htmlResponse="<table border='1'><tr><th>Campaig name</th><th>Campaign description</th></tr>";
+        for (Campaign campaign: listCampaigns){
+            htmlResponse+="<tr><td>"+campaign.name+"</td><td>"+campaign.description+"</td></tr>";
+        }
+        htmlResponse+="<table><div><a href='/finance'>Back</a></div> ";
+       
+        return htmlResponse;
+    }
+
+}

+ 25 - 0
finance-webapp/src/main/java/com/example/CampaignService.java

@@ -0,0 +1,25 @@
+package com.example;
+
+import java.util.Set;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+import com.example.model.Campaign;
+
+import io.quarkus.oidc.token.propagation.AccessToken;
+
+
+@Path("/campaign")
+@RegisterRestClient
+@AccessToken
+public interface CampaignService {
+    
+    @GET
+    @Path("/list")
+    @Produces(MediaType.APPLICATION_JSON)
+    Set<Campaign> getAll();
+}

+ 18 - 0
finance-webapp/src/main/java/com/example/ForbiddenExceptionMapper.java

@@ -0,0 +1,18 @@
+package com.example;
+
+
+import javax.annotation.Priority;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import io.quarkus.security.ForbiddenException;
+
+@Provider//register as JAXRS provider
+@Priority(1)//override the build-int mapper(which has 5001 prio)
+public class ForbiddenExceptionMapper implements ExceptionMapper<ForbiddenException> {
+  @Override
+  public Response toResponse(ForbiddenException exception) {   
+    return Response.serverError().entity("{\"message\":\"You are not in the required role\"}").build();    
+  }
+}

+ 43 - 0
finance-webapp/src/main/java/com/example/GlobalExceptionMapper.java

@@ -0,0 +1,43 @@
+package com.example;
+
+import javax.annotation.Priority;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import io.quarkus.security.ForbiddenException;
+import io.quarkus.security.UnauthorizedException;
+
+@Provider//register as JAXRS provider
+@Priority(1)//override the build-int mapper(which has 5001 prio)
+public class GlobalExceptionMapper implements ExceptionMapper<Exception>{
+  @Override
+  public Response toResponse(Exception exception) {   
+    return mapExceptionToResponse(exception);    
+  }
+  private Response mapExceptionToResponse(Exception exception) {
+    exception.printStackTrace();
+    // Use response from WebApplicationException as they are
+    if (exception instanceof WebApplicationException) {
+      
+      // Overwrite error message
+      Response originalErrorResponse = ((WebApplicationException) exception).getResponse();
+      return Response.fromResponse(originalErrorResponse)
+                     .entity(originalErrorResponse.getStatusInfo().getReasonPhrase())
+                     .build();
+    }
+    // Special mappings
+    else if (exception instanceof UnauthorizedException) {      
+      return Response.status(401).entity(exception.getMessage()).build();
+    }
+   
+    else if (exception instanceof IllegalArgumentException) {      
+      return Response.status(400).entity("You need to be authenticated").build();
+    }
+    // Use 500 (Internal Server Error) for all other
+    else {
+      return Response.serverError().entity("Internal Server Error").build();
+    }
+  }
+}

+ 64 - 0
finance-webapp/src/main/java/com/example/ShowInvoices.java

@@ -0,0 +1,64 @@
+package com.example;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.json.simple.JSONArray;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import io.quarkus.qute.Template;
+import io.quarkus.qute.TemplateInstance;
+
+@Path("/showinvoices")
+@RolesAllowed("finance-user")
+public class ShowInvoices {
+
+    private final Template showInvoices;    
+    JSONArray invoices = new JSONArray();
+    String jsonFileName = "invoices.json";
+
+    public ShowInvoices(Template showInvoices) {
+        this.showInvoices = requireNonNull(showInvoices, "page is required");
+    }
+
+    public Object read_invoices() throws FileNotFoundException, IOException, URISyntaxException, ParseException {
+        URL fileResource = this.getClass().getClassLoader().getResource( jsonFileName );
+        File jsonResourceFile = new File( fileResource.toURI() );
+        return new JSONParser().parse( new FileReader( jsonResourceFile) );
+    }
+
+    @GET    
+    @Produces(MediaType.APPLICATION_JSON)
+    public TemplateInstance get() {
+
+        // Get invoice data from Json file
+        try{
+          invoices = (JSONArray) read_invoices();
+        }catch(IOException|URISyntaxException|ParseException excp){
+          System.out.println("ERROR: "+excp);
+          excp.printStackTrace();
+          invoices = new JSONArray();
+          
+          //invoices.put("error", excp.toString());  
+        }        
+        TemplateInstance invoiceListTemplate=showInvoices.data("invoices", invoices);
+        return invoiceListTemplate;
+
+
+        
+    }
+
+}

+ 45 - 0
finance-webapp/src/main/java/com/example/ShowTokens.java

@@ -0,0 +1,45 @@
+package com.example;
+import io.quarkus.qute.Template;
+import io.quarkus.qute.TemplateInstance;
+import io.quarkus.security.Authenticated;
+
+import javax.inject.Inject;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+
+
+
+
+import static java.util.Objects.requireNonNull;
+
+@Path("/showtokens")
+@Authenticated
+public class ShowTokens {
+    
+    
+    private final Template showtokens;
+    
+    @Inject
+    JsonWebToken accessToken;
+
+    public ShowTokens(Template showtokens) {
+        
+        this.showtokens = requireNonNull(showtokens, "page is required");
+    }
+
+    @GET
+    @Produces(MediaType.TEXT_HTML)
+    public TemplateInstance get(@QueryParam("token") String token) {
+       
+        TemplateInstance theTemplate=showtokens.data("token", accessToken);
+        
+        return theTemplate;
+    }
+
+}

+ 6 - 0
finance-webapp/src/main/java/com/example/model/Campaign.java

@@ -0,0 +1,6 @@
+package com.example.model;
+
+public class Campaign {
+    public String name;
+    public String description;
+}

+ 79 - 0
finance-webapp/src/main/resources/META-INF/resources/index.html

@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <title>Quarkus Finance Web Application</title>
+
+    <script type="text/javascript">
+        function changeContent(url) {
+            document.getElementById("messages").innerHTML = { contentMessages };
+            return (false);
+        }
+        function showInvoiceForm() {
+            document.getElementById("messages").innerHTML = "<form action='addinvoices' method='get'>"
+                + "<br/>Invoice ID: <input type='text' id='invoiceId' name='invoiceId'/>"
+                + "<br/>Customer: <input type='text' id='name' name='name'/>"
+                + "<br/>Product: <input type='text' id='item' name='item'/>"
+                + "<br/>Date: <input type='text' id='date' name='date'/>"
+                + "<br/>Amount: <input type='text' id='amount' name='amount'/>"
+                + "<br/><input type='submit'/>"
+                + "</form>"
+        }
+        function showCampaignList(e){
+            (e || window.event).preventDefault();
+
+            fetch("showcampaigns/list")
+                .then((response) => response.text())
+                .then((html) => {
+                    document.getElementById("messages").innerHTML = html;
+                })
+                .catch((error) => {
+                    console.warn(error);
+                    document.getElementById("messages").innerHTML = error;
+                });
+        }
+    </script>
+    <link rel="stylesheet" type="text/css" href="styles.css" />
+
+</head>
+
+<body>
+
+    <div class="banner-wrapper">
+        <div class="grid">
+            <div class="banner">
+                <div class="grid">
+                    <div class="callout">Quarkus Finance Web Application</div>
+                    <div class="app-details">
+                        <h5>Red Hat Training.</h5>
+                        <ul>
+                            <li>Java Quarkus</li>
+                            <li>OIDC integration with <code>quarkus-oidc</code> extension.</li>
+                            <li>Authentication Code Flow with confidential client.</li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrapper">
+        <div class="grid">
+            <div class="left-column">
+                <br />&rsaquo;<code>@Path protected by Java annotation @Authenticated <a href="showtokens" class="path-link">Show my access token</a></code>
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @RolesAllowed("finance-user") <a href="showinvoices" class="path-link">/showinvoices</a></code>
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @RolesAllowed("finance-admin") <a onclick="javascript:showInvoiceForm();return false;" class="path-link">/addinvoices </a></code>
+                <!--      <br />&rsaquo; <code>@Path public: <a href="showcampaigns/list" class="path-link">External API: http://localhost:3000/campaign/list</a></code>-->
+                <br />&rsaquo;
+                <code>@Path protected in external API(authenticated user) <a onclick="javascript:showCampaignList();return false;" class="path-link">External API: http://localhost:3000/campaign/list</a></code>
+            </div>
+            <div class="right-column" id="messages">
+                
+            </div>
+        </div>
+    </div>
+</body>
+
+</html>

Різницю між файлами не показано, бо вона завелика
+ 76 - 0
finance-webapp/src/main/resources/META-INF/resources/styles.css


+ 16 - 0
finance-webapp/src/main/resources/application.properties

@@ -0,0 +1,16 @@
+quarkus.oidc.auth-server-url=https://RHSSO-SERVER-URL/auth/realms/REALM-NAME
+quarkus.oidc.client-id=CLIENT-ID
+quarkus.oidc.credentials.secret=CHANGEME
+quarkus.oidc.application-type=web-app
+#For web-app type applications, Quarkus needs to read the roles from access token, instead of the idToken:
+quarkus.oidc.roles.source=accesstoken
+
+quarkus.http.root-path=/finance
+
+#Config for external endpoint call:
+quarkus.rest-client."com.example.CampaignService".url=http://localhost:3000
+quarkus.rest-client."com.example.CampaignService".scope=javax.inject.Singleton
+
+#OIDC verbose logging:
+quarkus.log.category."io.quarkus.oidc".min-level=TRACE
+quarkus.log.category."io.quarkus.oidc".level=TRACE

+ 23 - 0
finance-webapp/src/main/resources/invoices.json

@@ -0,0 +1,23 @@
+[
+    {
+        "id": 12120,
+        "name": "John Doe",
+        "item": "Refrigerator",
+        "date": "01-01-2022",
+        "amount": "440"
+    },
+    {
+        "id": 12121,
+        "name": "Alice in Chains",
+        "item": "Vacuum Cleaner",
+        "date": "10-02-2022",
+        "amount": "300"
+    },
+    {
+        "id": 12122,
+        "name": "John Doe",
+        "item": "Kettle",
+        "date": "11-08-2022",
+        "amount": "130"
+    }
+]

+ 82 - 0
finance-webapp/src/main/resources/templates/addInvoice.html

@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html lang="en">
+
+    <head>
+        <meta charset="UTF-8">
+        <title>Quarkus Finance Web Application</title>
+    
+        <script type="text/javascript">
+            function changeContent(url) {
+                document.getElementById("messages").innerHTML = { contentMessages };
+                return (false);
+            }
+            function showInvoiceForm() {
+                document.getElementById("messages").innerHTML = "<form action='addinvoices' method='get'>"
+                    + "<br/>Invoice ID: <input type='text' id='invoiceId' name='invoiceId'/>"
+                    + "<br/>Customer: <input type='text' id='name' name='name'/>"
+                    + "<br/>Product: <input type='text' id='item' name='item'/>"
+                    + "<br/>Date: <input type='text' id='date' name='date'/>"
+                    + "<br/>Amount: <input type='text' id='amount' name='amount'/>"
+                    + "<br/><input type='submit'/>"
+                    + "</form>"
+            }
+            function showCampaignList(e){
+                (e || window.event).preventDefault();
+    
+                fetch("showcampaigns/list")
+                    .then((response) => response.text())
+                    .then((html) => {
+                        document.getElementById("messages").innerHTML = html;
+                    })
+                    .catch((error) => {
+                        console.warn(error);
+                        document.getElementById("messages").innerHTML = error;
+                    });
+            }
+        </script>
+        <link rel="stylesheet" type="text/css" href="styles.css" />
+    
+    </head>
+
+<body>
+
+    <div class="banner-wrapper">
+        <div class="grid">
+            <div class="banner">
+                <div class="grid">
+                    <div class="callout">Quarkus Finance Web Application</div>
+                    <div class="app-details">
+                        <h5>Red Hat Training</h5>
+                        <ul>
+                            <li>Java Quarkus</li>
+                            <li>OIDC integration with <code>quarkus-oidc</code> extension.</li>
+                            <li>Authentication Code Flow with confidential client.</li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrapper">
+        <div class="grid">
+            <div class="left-column">
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @Authenticated <a href="showtokens" class="path-link">Show my access token</a></code>
+
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @RolesAllowed("finance-user") <a href="showinvoices" class="path-link">/showinvoices</a></code>
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @RolesAllowed("finance-admin") <a onclick="javascript:showInvoiceForm();return false;" class="path-link">/addinvoices </a></code>
+                <!--      <br />&rsaquo; <code>@Path public: <a href="showcampaigns/list" class="path-link">External API: http://localhost:3000/campaign/list</a></code>-->
+                <br />&rsaquo;
+                <code>@Path protected in external API(authenticated user) <a onclick="javascript:showCampaignList();return false;" class="path-link">External API: http://localhost:3000/campaign/list</a></code>
+            </div>
+            <div class="right-column" id="messages">
+                {message}
+            </div>
+        </div>
+    </div>
+
+</body>
+
+</html>

+ 95 - 0
finance-webapp/src/main/resources/templates/showInvoices.html

@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html lang="en">
+
+    <head>
+        <meta charset="UTF-8">
+        <title>Quarkus Finance Web Application</title>
+    
+        <script type="text/javascript">
+            function changeContent(url) {
+                document.getElementById("messages").innerHTML = { contentMessages };
+                return (false);
+            }
+            function showInvoiceForm() {
+                document.getElementById("messages").innerHTML = "<form action='addinvoices' method='get'>"
+                    + "<br/>Invoice ID: <input type='text' id='invoiceId' name='invoiceId'/>"
+                    + "<br/>Customer: <input type='text' id='name' name='name'/>"
+                    + "<br/>Product: <input type='text' id='item' name='item'/>"
+                    + "<br/>Date: <input type='text' id='date' name='date'/>"
+                    + "<br/>Amount: <input type='text' id='amount' name='amount'/>"
+                    + "<br/><input type='submit'/>"
+                    + "</form>"
+            }
+            function showCampaignList(e){
+                (e || window.event).preventDefault();
+    
+                fetch("showcampaigns/list")
+                    .then((response) => response.text())
+                    .then((html) => {
+                        document.getElementById("messages").innerHTML = html;
+                    })
+                    .catch((error) => {
+                        console.warn(error);
+                        document.getElementById("messages").innerHTML = error;
+                    });
+            }
+        </script>
+        <link rel="stylesheet" type="text/css" href="styles.css" />
+    
+    </head>
+<body>
+
+    <div class="banner-wrapper">
+        <div class="grid">
+            <div class="banner">
+                <div class="grid">
+                    <div class="callout">Quarkus Finance Web Application</div>
+                    <div class="app-details">
+                        <h5>Red Hat Training</h5>
+                        <ul>
+                            <li>Java Quarkus</li>
+                            <li>OIDC integration with <code>quarkus-oidc</code> extension.</li>
+                            <li>Authentication Code Flow with confidential client.</li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrapper">
+        <div class="grid">
+            <div class="left-column">
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @Authenticated <a href="showtokens" class="path-link">Show my access token</a></code>
+
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @RolesAllowed("finance-user") <a href="showinvoices" class="path-link">/showinvoices</a></code>
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @RolesAllowed("finance-admin") <a onclick="javascript:showInvoiceForm();return false;" class="path-link">/addinvoices </a></code>
+                <!--      <br />&rsaquo; <code>@Path public: <a href="showcampaigns/list" class="path-link">External API: http://localhost:3000/campaign/list</a></code>-->
+                <br />&rsaquo;
+                <code>@Path protected in external API(authenticated user) <a onclick="javascript:showCampaignList();return false;" class="path-link">External API: http://localhost:3000/campaign/list</a></code>
+               
+                
+            </div>
+            <div class="right-column" id="messages">
+                <table border="1">
+                    <tr><th>Invoice ID</th> <th>Item</th><th>Customer</th><th>Date</th><th>Amount (€)</th>                        </tr>
+                    {#for invoice in invoices} 
+                    <tr>
+                       <td>{invoice.id}</td>
+                       <td>{invoice.item}</td>
+                        <td>{invoice.name}</td>
+                       <td>{invoice.date}</td>
+                       <td>{invoice.amount}</td>
+                    </tr>
+                    {/for}
+                </table>
+                <div><a href="/finance">Back</a></div> 
+            </div>
+        </div>
+    </div>
+
+</body>
+
+</html>

+ 94 - 0
finance-webapp/src/main/resources/templates/showtokens.html

@@ -0,0 +1,94 @@
+
+{@org.eclipse.microprofile.jwt.JsonWebToken token}
+<!DOCTYPE html>
+<html lang="en">
+
+    <head>
+        <meta charset="UTF-8">
+        <title>Quarkus Finance Web Application</title>
+    
+        <script type="text/javascript">
+            function changeContent(url) {
+                document.getElementById("messages").innerHTML = { contentMessages };
+                return (false);
+            }
+            function showInvoiceForm() {
+                document.getElementById("messages").innerHTML = "<form action='addinvoices' method='get'>"
+                    + "<br/>Invoice ID: <input type='text' id='invoiceId' name='invoiceId'/>"
+                    + "<br/>Customer: <input type='text' id='name' name='name'/>"
+                    + "<br/>Product: <input type='text' id='item' name='item'/>"
+                    + "<br/>Date: <input type='text' id='date' name='date'/>"
+                    + "<br/>Amount: <input type='text' id='amount' name='amount'/>"
+                    + "<br/><input type='submit'/>"
+                    + "</form>"
+            }
+            function showCampaignList(e){
+                (e || window.event).preventDefault();
+    
+                fetch("showcampaigns/list")
+                    .then((response) => response.text())
+                    .then((html) => {
+                        document.getElementById("messages").innerHTML = html;
+                    })
+                    .catch((error) => {
+                        console.warn(error);
+                        document.getElementById("messages").innerHTML = error;
+                    });
+            }
+        </script>
+        <link rel="stylesheet" type="text/css" href="styles.css" />
+    
+    </head>
+
+<body>
+
+    <div class="banner-wrapper">
+        <div class="grid">
+            <div class="banner">
+                <div class="grid">
+                    <div class="callout">Quarkus Finance Web Application</div>
+                    <div class="app-details">
+                        <h5>Red Hat Training</h5>
+                        <ul>
+                            <li>Java Quarkus</li>
+                            <li>OIDC integration with <code>quarkus-oidc</code> extension.</li>
+                            <li>Authentication Code Flow with confidential client.</li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrapper">
+        <div class="grid">
+            <div class="left-column">
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @Authenticated <a href="showtokens" class="path-link">Show my access token</a></code>
+
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @RolesAllowed("finance-user") <a href="showinvoices" class="path-link">/showinvoices</a></code>
+                <br />&rsaquo;
+                <code>@Path protected by Java annotation @RolesAllowed("finance-admin") <a onclick="javascript:showInvoiceForm();return false;" class="path-link">/addinvoices </a></code>
+                <!--      <br />&rsaquo; <code>@Path public: <a href="showcampaigns/list" class="path-link">External API: http://localhost:3000/campaign/list</a></code>-->
+                <br />&rsaquo;
+                <code>@Path protected in external API(authenticated user) <a onclick="javascript:showCampaignList();return false;" class="path-link">External API: http://localhost:3000/campaign/list</a></code>
+                <br />&rsaquo; <b>Hi, {token.getName()}. Your access token claims :</b>
+
+            </div>
+            <!-- div class="right-column" id="messages" style="font-size: 14px;grid-column: span 8;;" -->
+            <div class="right-column" id="messages" >
+                <table style="font-family: 'Courier New', Courier, monospace; font-size: 12px ;">
+                    {#for claim in token.getClaimNames()}
+                      <tr><td>{claim}</td><td>{token.getClaim(claim)}</td></tr>
+                    {/for}
+                </table>
+                
+                
+                <div><a href="/finance">Back</a></div> 
+              
+            </div>
+        </div>
+    </div>
+</body>
+
+</html>

+ 80 - 0
marketing-frontend/README.md

@@ -0,0 +1,80 @@
+app-nodejs-html5: HTML5 Service Invocation Application
+===================================================
+
+Level: Beginner
+Technologies: HTML5, JavaScript
+Summary: HTML5 Service Invocation Application run under Express
+Target Product: Keycloak
+Source: <https://github.com/keycloak/keycloak-quickstarts>
+
+What is it?
+-----------
+
+The `app-nodejs-html5` quickstart demonstrates how to write an application with HTML5 and JavaScript that authenticates
+using Keycloak. Once authenticated the application shows how to invoke a service secured with Keycloak.
+
+System Requirements
+-------------------
+
+You need to have Node.js version 12.x or later installed.
+
+The quickstart requires that you have the [service-nodejs](../service-nodejs/README.md) running. It assumes the
+services are located at `http://localhost:3000/service`. If the services are running elsewhere you need to edit
+`app.js` and replace the value of `serviceUrl`.
+
+Configuration in Keycloak
+-----------------------
+
+Prior to running the quickstart you need to create a client in Keycloak and download the installation file.
+
+The following steps show how to create the client required for this quickstart:
+
+* Open the Keycloak admin console
+* Select `Clients` from the menu
+* Click `Create`
+* Add the following values:
+  * Client ID: You choose (for example `marketing-html5`)
+  * Client Protocol: `openid-connect`
+  * Root URL: URL to the application (for example `http://localhost:8080/marketing-html5`).
+* Click `Save`
+
+If you deploy the application somewhere else change the hostname and port of the URLs accordingly.
+
+Once saved you need to change the `Access Type` to `public` and click save.
+
+Finally you need to configure the JavaScript adapter, this is done by retrieving the adapter configuration file:
+
+* Click on `Installation` in the tab for the client you created
+* Select `Keycloak OIDC JSON`
+* Click `Download`
+* Move the file `keycloak.json` to the `src/main/webapp` directory in the root of the quickstart
+
+As an alternative you can create the client by importing the file [client-import.json](config/client-import.json) and
+copying [config/keycloak-example.json](config/keycloak-example.json) to `src/main/webapp/keycloak.json`.
+
+
+Build and Deploy the Quickstart
+--------------------------------
+
+1. Open a terminal and navigate to the root directory of this quickstart.
+
+2. The following shows the command to deploy the quickstart:
+
+   ````
+   npm install
+   npm run start
+   ````
+
+Access the Quickstart
+---------------------
+
+You can access the application with the following URL: <http://localhost:8080/marketing-html5>.
+
+The application provides buttons that allows invoking the different endpoints on the service:
+
+* Invoke public - Invokes the public endpoint and doesn't require a user to be logged-in
+* Invoke secured - Invokes the secured endpoint and requires a user with the role `user` to be logged-in
+* Invoke admin - Invokes the secured endpoint and requires a user with the role `admin` to be logged-in
+
+If you invoke the endpoints without the required permissions an error will be shown.
+

+ 9 - 0
marketing-frontend/app.js

@@ -0,0 +1,9 @@
+const express = require('express');
+const app = express();
+const port = 8081;
+
+app.use('/marketing-html5', express.static('src/main/webapp/'));
+
+app.listen(port, () => {
+  console.log(`marketing-html5 listening on port ${port}`);
+});

+ 29 - 0
marketing-frontend/package.json

@@ -0,0 +1,29 @@
+{
+  "name": "marketing-html5",
+  "version": "0.0.1",
+  "description": "HTML5 Service Invocation Application",
+  "main": "app.js",
+  "type": "module",
+  "scripts": {
+    "start": "node app.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/keycloak/keycloak-quickstarts.git"
+  },
+  "keywords": [
+    "keyclock",
+    "node.js",
+    "express"
+  ],
+  "author": "Red Hat Inc.",
+  "license": "Apache-2.0",
+  "bugs": {
+    "url": "https://github.com/keycloak/keycloak-quickstarts/issues"
+  },
+  "homepage": "https://github.com/keycloak/keycloak-quickstarts#readme",
+  "dependencies": {
+    "express": "^4.17.2",
+    "jwt-decode": "^3.1.2"
+  }
+}

+ 70 - 0
marketing-frontend/src/main/webapp/app.js

@@ -0,0 +1,70 @@
+
+var keycloak = new Keycloak();
+var serviceUrl = 'http://localhost:3000/campaign'
+
+function notAuthenticated() {
+    document.getElementById('not-authenticated').style.display = 'block';
+    document.getElementById('authenticated').style.display = 'none';
+}
+
+function authenticated() {
+    document.getElementById('not-authenticated').style.display = 'none';
+    document.getElementById('authenticated').style.display = 'block';
+    document.getElementById('message').innerHTML = 'User: ' + keycloak.tokenParsed['preferred_username'];
+}
+
+function showTokens(){
+   
+    document.getElementById("idtoken").innerHTML = "ID Token</br>"+JSON.stringify(jwt_decode(keycloak.idToken), null, '  ');
+    document.getElementById("accesstoken").innerHTML = "Access Token (bearer)</br>"+JSON.stringify(jwt_decode(keycloak.token), null, '  ');
+    document.getElementById("refreshtoken").innerHTML = "Refresh Token</br>"+JSON.stringify(jwt_decode(keycloak.refreshToken), null, '  ');
+   //document.getElementById("output").innerHTML = jwt_decode(keycloak.showToken);
+}
+function request(endpoint) {
+    var req = function() {
+        var req = new XMLHttpRequest();
+        var output = document.getElementById('message');
+        req.open('GET', serviceUrl + '/' + endpoint, true);
+
+        if (keycloak.authenticated) {
+           req.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
+           //req.setRequestHeader('Authorization', 'Bearer ' + keycloak.idToken); --> does not have roles, by default
+        }
+
+        req.onreadystatechange = function () {
+            if (req.readyState == 4) {
+                if (req.status == 200) {
+                    output.innerHTML =  JSON.stringify(req.responseText);
+                } else if (req.status == 0) {
+                    output.innerHTML = '<span class="error">Request to Marketing API failed</span>';
+                } else if (req.status == 403) {
+                    output.innerHTML = 'You have not the required role: <span class="error">' + req.status + ' ' + req.statusText + '</span>';
+                } else {
+                    output.innerHTML = 'Error: <span class="error">' + req.status + ' ' + req.statusText + '</span>';
+                }
+            }
+        };
+
+        req.send();
+    };
+
+    if (keycloak.authenticated) {
+        keycloak.updateToken(30).success(req);
+    } else {
+        req();
+    }
+}
+
+window.onload = function () {
+    keycloak.init({ onLoad: 'check-sso', checkLoginIframeInterval: 1 }).success(function () {
+        if (keycloak.authenticated) {
+            authenticated();
+        } else {
+            notAuthenticated();
+        }
+
+        document.body.style.display = 'block';
+    });
+}
+
+keycloak.onAuthLogout = notAuthenticated;

+ 59 - 0
marketing-frontend/src/main/webapp/index.html

@@ -0,0 +1,59 @@
+
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>Marketing Single Page Application</title>
+
+    <link rel="stylesheet" type="text/css" href="styles.css"/>
+    <script src="jwt-decode.min.js"></script>
+    <script src="keycloak.js"></script>
+    <script src="app.js"></script>
+</head>
+
+<body>
+    <div class="banner-wrapper">
+        <div class="grid">
+            <div class="banner">
+                <div class="grid">
+                    <div class="callout">Marketing Single Page Application</div>
+                    <div class="app-details">
+                        <h5>Red Hat Training.</h5>
+                        <ul>
+                            <li>Single Page Application HTML5+Javascript</li>
+                            <li>OIDC integration with RH-SSO Javascript Adapter.</li>
+                            <li>Authentication Code Flow with public client.</li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="wrapper">
+        <div id="not-authenticated" class="menu">
+            <button name="loginBtn" onclick="keycloak.login()">Login</button>
+        </div>
+
+        <div id="authenticated" class="menu">
+            <button name="tokenBtn" onclick="showTokens()">Token</button>
+            <button name="logoutBtn" onclick="keycloak.logout()">Logout</button>
+            <button name="accountBtn" onclick="keycloak.accountManagement()">RH-SSO Account Management Console</button>
+        </div>
+
+        <div class="content">
+            <button name="publicBtn" onclick="request('list')">List public marketing campaigns</button>
+            <div class="message" id="message"></div>
+            
+        </div>
+        <div class="content" id="tokens">
+            
+            <pre class="output" id="idtoken"></pre>
+          
+            <pre class="output" id="accesstoken"></pre>
+           
+            <pre class="output" id="refreshtoken"></pre>
+        </div>
+    </div>
+</body>
+
+</html>

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
marketing-frontend/src/main/webapp/jwt-decode.min.js


Різницю між файлами не показано, бо вона завелика
+ 15 - 0
marketing-frontend/src/main/webapp/keycloak.js


+ 8 - 0
marketing-frontend/src/main/webapp/keycloak.json

@@ -0,0 +1,8 @@
+{
+  "realm": "REALM-NAME",
+  "auth-server-url": "https://RHSSO-SERVER-URL/auth/",
+  "ssl-required": "external",
+  "resource": "CLIENT-ID",
+  "public-client": true,
+  "confidential-port": 0
+}

Різницю між файлами не показано, бо вона завелика
+ 75 - 0
marketing-frontend/src/main/webapp/styles.css


+ 107 - 0
marketing-restful-api/app.js

@@ -0,0 +1,107 @@
+
+
+const express = require('express');
+const session = require('express-session');
+const bodyParser = require('body-parser');
+
+const Keycloak = require('keycloak-connect');
+const cors = require('cors');
+
+const audit = require('express-requests-logger');
+const app = express();
+app.use(bodyParser.json());
+
+// Enable CORS support
+app.use(cors());
+
+//request logger:
+app.use(audit());
+/*app.use(audit({
+  
+  request: {
+      excludeBody: '*', // Exclude all body
+  },
+  response: {
+      excludeBody: '*' // Exclude all body from responses
+  }
+}));*/
+
+// Create a session-store to be used by both the express-session
+// middleware and the keycloak middleware.
+
+const memoryStore = new session.MemoryStore();
+
+app.use(session({
+  secret: 'f60OrkxQNIlIv8P9BbD69pH62dq1ySeE',
+  resave: false,
+  saveUninitialized: true,
+  store: memoryStore
+}));
+
+// Provide the session store to the Keycloak so that sessions
+// can be invalidated from the Keycloak console callback.
+//
+// Additional configuration is read from keycloak.json file
+// installed from the Keycloak web console.
+
+const keycloak = new Keycloak({
+  store: memoryStore
+});
+
+app.use(keycloak.middleware({
+  logout: '/logout',
+  admin: '/admin'
+}));
+
+app.get('/campaign/list', keycloak.protect(), function (req, res) {
+  console.log("Listing campaigns");
+  
+  if (res.status == 403) {
+    console.log("You need to be authenticated");
+    res.json({ message: 'You need to be authenticated' });
+  } else {
+    //res.json({message: 'You can list the campaigns'});
+    res.json(
+      [
+        { 'name': 'New Product announce', 'description':'We are releasing a new product' },
+        { 'name': 'Summer Time Season', 'description':'Summer is coming' },
+        { 'name': 'Singles day Promotions', 'description':'We have big discounts for singles!!!' },
+        { 'name': 'Spring Collection', 'description':'Spring is coming' },
+        { 'name': 'Black Friday Discounts', 'description':'Almost everything for free' }]
+      );
+  }
+
+});
+
+/*app.get('/campaign/add', keycloak.protect('realm:marketing-user'), function (req, res) {
+  logTokens(req);
+  
+    if (res.status == 403) {
+      res.json({message: 'You need the marketing-user role'});
+    } else {
+      res.json({message: 'You can add a campaign'});
+    }
+  
+  
+});
+
+app.get('/campaign/delete', keycloak.protect('realm:marketing-admin'), function (req, res) {
+  logTokens(req);
+  
+    if (res.status == 403) {
+      //res.json({message: 'You need the marketing-admin role'});
+      res.send(403,'You need the marketing-admin role');
+    } else{
+      res.json({message: 'You can select one to delete'});
+    }
+  
+  
+});*/
+
+app.use('*', function (req, res) {
+  res.send('Not found!');
+});
+
+app.listen(3000, function () {
+  console.log('Started at port 3000');
+});

+ 107 - 0
marketing-restful-api/app.js-authz

@@ -0,0 +1,107 @@
+
+
+const express = require('express');
+const session = require('express-session');
+const bodyParser = require('body-parser');
+
+const Keycloak = require('keycloak-connect');
+const cors = require('cors');
+
+const audit = require('express-requests-logger');
+const app = express();
+app.use(bodyParser.json());
+
+// Enable CORS support
+app.use(cors());
+
+//request logger:
+app.use(audit());
+/*app.use(audit({
+  
+  request: {
+      excludeBody: '*', // Exclude all body
+  },
+  response: {
+      excludeBody: '*' // Exclude all body from responses
+  }
+}));*/
+
+// Create a session-store to be used by both the express-session
+// middleware and the keycloak middleware.
+
+const memoryStore = new session.MemoryStore();
+
+app.use(session({
+  secret: 'f60OrkxQNIlIv8P9BbD69pH62dq1ySeE',
+  resave: false,
+  saveUninitialized: true,
+  store: memoryStore
+}));
+
+// Provide the session store to the Keycloak so that sessions
+// can be invalidated from the Keycloak console callback.
+//
+// Additional configuration is read from keycloak.json file
+// installed from the Keycloak web console.
+
+const keycloak = new Keycloak({
+  store: memoryStore
+});
+
+app.use(keycloak.middleware({
+  logout: '/logout',
+  admin: '/admin'
+}));
+
+app.get('/campaign/list', keycloak.protect('realm:marketing-user'), function (req, res) {
+  console.log("Listing campaigns");
+  
+  if (res.status == 403) {
+    console.log("You need to be authenticated");
+    res.json({ message: 'You need to be authenticated' });
+  } else {
+    //res.json({message: 'You can list the campaigns'});
+    res.json(
+      [
+        { 'name': 'New Product announce', 'description':'We are releasing a new product' },
+        { 'name': 'Summer Time Season', 'description':'Summer is coming' },
+        { 'name': 'Singles day Promotions', 'description':'We have big discounts for singles!!!' },
+        { 'name': 'Spring Collection', 'description':'Spring is coming' },
+        { 'name': 'Black Friday Discounts', 'description':'Almost everything for free' }]
+      );
+  }
+
+});
+
+/*app.get('/campaign/add', keycloak.protect('realm:marketing-user'), function (req, res) {
+  logTokens(req);
+  
+    if (res.status == 403) {
+      res.json({message: 'You need the marketing-user role'});
+    } else {
+      res.json({message: 'You can add a campaign'});
+    }
+  
+  
+});
+
+app.get('/campaign/delete', keycloak.protect('realm:marketing-admin'), function (req, res) {
+  logTokens(req);
+  
+    if (res.status == 403) {
+      //res.json({message: 'You need the marketing-admin role'});
+      res.send(403,'You need the marketing-admin role');
+    } else{
+      res.json({message: 'You can select one to delete'});
+    }
+  
+  
+});*/
+
+app.use('*', function (req, res) {
+  res.send('Not found!');
+});
+
+app.listen(3000, function () {
+  console.log('Started at port 3000');
+});

+ 8 - 0
marketing-restful-api/keycloak.json

@@ -0,0 +1,8 @@
+{
+  "realm": "REALM-NAME",
+  "bearer-only": true,
+  "auth-server-url": "https://RHSSO-SERVER-URL/auth/",
+  "ssl-required": "external",
+  "resource": "CLIENT-ID",
+  "confidential-port": 0
+}

+ 32 - 0
marketing-restful-api/package.json

@@ -0,0 +1,32 @@
+{
+  "name": "marketing-restful-api",
+  "version": "0.0.1",
+  "scripts": {
+    "start": "node app.js",
+    "test": "tape test/*.js"
+  },
+  "dependencies": {
+    "body-parser": "^1.13.3",
+    "cors": "^2.8.1",
+    "express": "^4.13.3",
+    "express-requests-logger": "^3.0.4",
+    "express-session": "^1.14.2",
+    "jwt-decode": "^3.1.2",
+    "keycloak-connect": "keycloak/keycloak-nodejs-connect"
+  },
+  "devDependencies": {
+    "eslint": "^3.3.1",
+    "eslint-config-semistandard": "^7.0.0-beta.0",
+    "eslint-config-standard": "^6.0.0",
+    "eslint-plugin-promise": "^3.3.0",
+    "eslint-plugin-react": "^6.1.2",
+    "eslint-plugin-standard": "^2.0.0",
+    "ink-docstrap": "^1.1.4",
+    "jsdoc": "^3.4.0",
+    "jshint": "^2.13.6",
+    "keycloak-client-registration": "^0.1.0",
+    "keycloak-request-token": "^0.1.0",
+    "roi": "^0.15.0",
+    "tape": "^4.5.1"
+  }
+}

Деякі файли не було показано, через те що забагато файлів було змінено